aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--main.html16
-rw-r--r--modules-index.json116
-rw-r--r--modules/core/core-bundle.js19
-rw-r--r--modules/core/module-aiassist.js9
-rw-r--r--modules/core/module-loader.js2
-rw-r--r--modules/core/terminal.js10
-rw-r--r--modules/ext/critical-ext-bundle.js183
-rw-r--r--modules/ext/editor.js47
-rw-r--r--modules/ext/filemgr.js136
-rw-r--r--modules/ext/media.js508
-rw-r--r--modules/ext/voice.js59
-rw-r--r--packages.json11
-rw-r--r--review-newos4.html28
-rw-r--r--skills/agent-orchestration.json4
-rw-r--r--skills/agentic-loop-optimization.json15
-rw-r--r--skills/backup-and-restore.json4
-rw-r--r--skills/debug-panel-and-logs.json4
-rw-r--r--skills/floating-ui.json15
-rw-r--r--skills/memory-graph-update.json13
-rw-r--r--skills/mindmap-user-guide.json14
-rw-r--r--skills/mindmap-workflow.json4
-rw-r--r--skills/module-audit-and-test.json9
-rw-r--r--skills/module-management.json4
-rw-r--r--skills/navigating-the-terminal.json4
-rw-r--r--skills/role-archetype-framework.json13
-rw-r--r--skills/security-and-safety.json4
-rw-r--r--skills/skills-system-guide.json4
-rw-r--r--skills/suggestion-engine.json9
-rw-r--r--skills/system-maintenance.json4
-rw-r--r--skills/web-search-and-fetch.json4
-rw-r--r--skills/webdav-operations.json4
31 files changed, 1186 insertions, 90 deletions
diff --git a/main.html b/main.html
index 1926d89..27cac74 100644
--- a/main.html
+++ b/main.html
@@ -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 &mdash; Terminal OS</span>
+ <span class="version-title">newOS4 v4.5.75 &mdash; 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 &amp; 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 &amp; 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