diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-01 06:00:39 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-01 06:00:39 +0200 |
| commit | 6aed9c9e0252b0c8f681a358e5a8707271454137 (patch) | |
| tree | 332a29a515c87c2295355cf81f18b818cfbaa846 | |
| parent | 7cc82d2d32e44ab1c5b7d042d5522d036d9aa0be (diff) | |
auto: 2026-06-01_06:00 changes
| -rw-r--r-- | cheatsheet.html | 166 | ||||
| -rw-r--r-- | help.html | 254 | ||||
| -rw-r--r-- | main.html | 73 | ||||
| -rw-r--r-- | modules/core/module-aiassist.js | 10 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 2 | ||||
| -rw-r--r-- | modules/core/ollama-manager.js | 72 | ||||
| -rw-r--r-- | modules/core/terminal.js | 58 | ||||
| -rw-r--r-- | modules/ext/busybox.js | 8 |
8 files changed, 589 insertions, 54 deletions
diff --git a/cheatsheet.html b/cheatsheet.html new file mode 100644 index 0000000..4cb4a1f --- /dev/null +++ b/cheatsheet.html @@ -0,0 +1,166 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="UTF-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>newOS4 — Quick Reference</title> +<style> +*,*::before,*::after{box-sizing:border-box;margin:0;padding:0} +:root{ + --bg:#0a0a14;--bg2:#111122;--bg3:#16162a;--text:#d0d0e8;--text2:#8888aa; + --accent:#7c5cfc;--accent2:#a78bfa;--green:#34d399;--cyan:#22d3ee;--amber:#fbbf24; + --rose:#fb7185;--border:#2a2a4a;--radius:8px; + --font-mono:'JetBrains Mono','Fira Code','Courier New',monospace; + --font-sans:-apple-system,BlinkMacSystemFont,'Segoe UI','Inter',sans-serif +} +body{background:var(--bg);color:var(--text);font-family:var(--font-sans);font-size:14px;line-height:1.5;padding:20px;-webkit-font-smoothing:antialiased} +h1{font-size:1.4em;color:#fff;margin-bottom:4px;letter-spacing:-.02em;display:flex;align-items:center;gap:8px} +.sub{color:var(--text2);font-size:.82em;margin-bottom:20px;border-bottom:1px solid var(--border);padding-bottom:12px} +.sub a{color:var(--accent2);text-decoration:none} +.sub a:hover{color:#fff} +h2{font-size:.95em;color:var(--accent2);margin:16px 0 8px;letter-spacing:.03em;text-transform:uppercase;display:flex;align-items:center;gap:6px} +.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:4px 12px;margin-bottom:4px} +.cmd{font-family:var(--font-mono);font-size:.85em;color:var(--accent2);font-weight:600} +.desc{color:var(--text2);font-size:.78em} +.item{padding:4px 0;border-bottom:1px solid rgba(42,42,74,.3);display:flex;gap:8px;align-items:baseline} +.item .cmd{min-width:80px;flex-shrink:0} +.cols{display:grid;grid-template-columns:1fr 1fr;gap:8px 24px} +@media(max-width:600px){.cols{grid-template-columns:1fr}} +pre{background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);padding:10px 14px;font-family:var(--font-mono);font-size:.82em;overflow-x:auto;margin:8px 0;line-height:1.6;color:var(--text)} +@media print{body{background:#fff;color:#222;padding:10mm;font-size:10px}:root{--bg:#fff;--bg2:#f5f5f5;--text:#222;--text2:#666;--accent2:#6366f1;--accent:#6366f1;--border:#ddd;--green:#16a34a;--rose:#dc2626}.sub{border-color:#ddd}.item{border-color:#eee}.cmd{color:#4f46e5}pre{background:#f5f5f5;border-color:#ddd;color:#333}.no-print{display:none!important}} +.footer{text-align:center;margin-top:24px;padding-top:12px;border-top:1px solid var(--border);color:var(--text2);font-size:.78em} +</style> +</head> +<body> + +<h1>⚡ newOS4 Quick Reference</h1> +<div class="sub">WebX11 v4.2 · <a href="help.html">Full docs →</a> · <span class="no-print">Ctrl+P to print</span></div> + +<div class="cols"> + +<div> +<h2>🟢 Basics</h2> +<div class="grid"> +<div class="item"><span class="cmd">help</span><span class="desc">Show all commands</span></div> +<div class="item"><span class="cmd">help <cmd></span><span class="desc">Details for a command</span></div> +<div class="item"><span class="cmd">ls</span><span class="desc">List files</span></div> +<div class="item"><span class="cmd">cd <dir></span><span class="desc">Change directory</span></div> +<div class="item"><span class="cmd">pwd</span><span class="desc">Print working dir</span></div> +<div class="item"><span class="cmd">clear / cls</span><span class="desc">Clear terminal</span></div> +<div class="item"><span class="cmd">cat <file></span><span class="desc">View file</span></div> +<div class="item"><span class="cmd">touch <file></span><span class="desc">Create empty file</span></div> +<div class="item"><span class="cmd">mkdir <dir></span><span class="desc">Create directory</span></div> +<div class="item"><span class="cmd">rm <file></span><span class="desc">Remove file</span></div> +<div class="item"><span class="cmd">cp <src> <dst></span><span class="desc">Copy file</span></div> +<div class="item"><span class="cmd">mv <old> <new></span><span class="desc">Move/rename</span></div> +<div class="item"><span class="cmd">edit <file></span><span class="desc">Open code editor</span></div> +<div class="item"><span class="cmd">find <name></span><span class="desc">Search files</span></div> +<div class="item"><span class="cmd">grep <str></span><span class="desc">Search inside files</span></div> +<div class="item"><span class="cmd">tree</span><span class="desc">Dir tree view</span></div> +</div> + +<h2>🤖 AI</h2> +<div class="grid"> +<div class="item"><span class="cmd">ai "..."</span><span class="desc">Ask AI assistant</span></div> +<div class="item"><span class="cmd">ask "..."</span><span class="desc">Alias for ai</span></div> +<div class="item"><span class="cmd">ai-model</span><span class="desc">List/switch model</span></div> +<div class="item"><span class="cmd">ai-clear</span><span class="desc">Reset conversation</span></div> +<div class="item"><span class="cmd">ai-system</span><span class="desc">Show/edit system prompt</span></div> +<div class="item"><span class="cmd">ai-context</span><span class="desc">View AI context</span></div> +<div class="item"><span class="cmd">ai-stop</span><span class="desc">Cancel AI</span></div> +</div> + +<h2>🧩 Modules</h2> +<div class="grid"> +<div class="item"><span class="cmd">modules / listmods</span><span class="desc">List loaded modules</span></div> +<div class="item"><span class="cmd">loadmod <url></span><span class="desc">Load module from URL</span></div> +<div class="item"><span class="cmd">startup-modules</span><span class="desc">Configure boot modules</span></div> +<div class="item"><span class="cmd">tutorial</span><span class="desc">Interactive tour</span></div> +</div> + +<h2>🦙 Ollama</h2> +<div class="grid"> +<div class="item"><span class="cmd">ollama</span><span class="desc">Open Ollama panel</span></div> +<div class="item"><span class="cmd">models</span><span class="desc">List available models</span></div> +<div class="item"><span class="cmd">model-pull <name></span><span class="desc">Download a model</span></div> +</div> + +<h2>📡 Network</h2> +<div class="grid"> +<div class="item"><span class="cmd">search <q></span><span class="desc">Web search</span></div> +<div class="item"><span class="cmd">fetch <url></span><span class="desc">Fetch web page</span></div> +<div class="item"><span class="cmd">ddg <q></span><span class="desc">DuckDuckGo search</span></div> +<div class="item"><span class="cmd">server</span><span class="desc">Start/status server</span></div> +</div> + +</div> + +<div> + +<h2>🔧 Tools</h2> +<div class="grid"> +<div class="item"><span class="cmd">calc <expr></span><span class="desc">Calculator</span></div> +<div class="item"><span class="cmd">cron-every <s> <cmd></span><span class="desc">Schedule repeating</span></div> +<div class="item"><span class="cmd">cron-list / cron-stop</span><span class="desc">Manage jobs</span></div> +<div class="item"><span class="cmd">wpm</span><span class="desc">Package manager</span></div> +<div class="item"><span class="cmd">mindmap</span><span class="desc">Brainstorming tool</span></div> +<div class="item"><span class="cmd">webdav-status</span><span class="desc">Cloud sync status</span></div> +<div class="item"><span class="cmd">webdav-push / pull</span><span class="desc">Sync files</span></div> +<div class="item"><span class="cmd">theme <name></span><span class="desc">Change theme</span></div> +<div class="item"><span class="cmd">voice</span><span class="desc">Speech-to-text</span></div> +<div class="item"><span class="cmd">chat</span><span class="desc">Chat overlay</span></div> +<div class="item"><span class="cmd">gzip / gunzip</span><span class="desc">Compress files</span></div> +<div class="item"><span class="cmd">encrypt / decrypt</span><span class="desc">AES-256 encrypt</span></div> +</div> + +<h2>🐚 Shell</h2> +<div class="grid"> +<div class="item"><span class="cmd">export <K>=<V></span><span class="desc">Set variable</span></div> +<div class="item"><span class="cmd">script <file.sh></span><span class="desc">Run shell script</span></div> +<div class="item"><span class="cmd">set</span><span class="desc">List variables</span></div> +<div class="item"><span class="cmd">cmd1 ; cmd2</span><span class="desc">Sequence</span></div> +<div class="item"><span class="cmd">cmd1 && cmd2</span><span class="desc">Chain (AND)</span></div> +<div class="item"><span class="cmd">cmd1 || cmd2</span><span class="desc">Chain (OR)</span></div> +<div class="item"><span class="cmd">cmd1 | cmd2</span><span class="desc">Pipe</span></div> +<div class="item"><span class="cmd">cmd > file</span><span class="desc">Redirect output</span></div> +</div> + +<h2>🖥️ System</h2> +<div class="grid"> +<div class="item"><span class="cmd">status</span><span class="desc">System health</span></div> +<div class="item"><span class="cmd">mem</span><span class="desc">Memory usage</span></div> +<div class="item"><span class="cmd">echo <text></span><span class="desc">Print text</span></div> +<div class="item"><span class="cmd">date / cal</span><span class="desc">Date & calendar</span></div> +<div class="item"><span class="cmd">whoami / hostname</span><span class="desc">User & machine</span></div> +<div class="item"><span class="cmd">logs <n></span><span class="desc">View recent logs</span></div> +<div class="item"><span class="cmd">logrotate</span><span class="desc">Rotate log files</span></div> +<div class="item"><span class="cmd">config</span><span class="desc">Config manager UI</span></div> +</div> + +<h2>🔒 Security</h2> +<div class="grid"> +<div class="item"><span class="cmd">security</span><span class="desc">Show current level</span></div> +<div class="item"><span class="cmd">security-level 0-3</span><span class="desc">Set level</span></div> +</div> +<div style="margin-top:8px"> +<p style="font-size:.78em;color:var(--text2)">Level 0 = permissive · 1 = standard (default) · 2 = paranoid · 3 = audit</p> +</div> + +<h2>⌨️ Shortcuts</h2> +<div class="grid"> +<div class="item"><span class="cmd">↑ / ↓</span><span class="desc">Command history</span></div> +<div class="item"><span class="cmd">Tab</span><span class="desc">Autocomplete</span></div> +<div class="item"><span class="cmd">Ctrl+K</span><span class="desc">Quick Commands panel</span></div> +<div class="item"><span class="cmd">Esc</span><span class="desc">Cancel AI / close</span></div> +<div class="item"><span class="cmd">Ctrl+L</span><span class="desc">Clear terminal</span></div> +</div> + +</div> +</div> + +<div class="footer"> + WebX11 v4.2 · <a href="help.html">help.html</a> · newOS4 on <a href="main.html">Launch →</a> +</div> + +</body> +</html> @@ -10,26 +10,26 @@ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent} :root{ --bg:#0a0a14; - --bg2:#111122; - --bg3:#16162a; - --bg4:#1c1c36; - --text:#d0d0e8; - --text2:#8888aa; - --accent:#7c5cfc; + --bg2:#141428; + --bg3:#1c1c36; + --bg4:#24244a; + --text:#e8e8ff; + --text2:#b0b0cc; + --accent:#818cf8; --accent2:#a78bfa; --green:#34d399; --cyan:#22d3ee; --amber:#fbbf24; --rose:#fb7185; --pink:#e879f9; - --border:#2a2a4a; + --border:#3a3a5a; --radius:12px; --font-mono:'JetBrains Mono','Fira Code','Cascadia Code','Courier New',monospace; --font-sans:-apple-system,BlinkMacSystemFont,'Segoe UI','Inter','SF Pro',Roboto,sans-serif } html{scroll-behavior:smooth} -body{background:var(--bg);color:var(--text);font-family:var(--font-sans);line-height:1.7;font-size:15px;-webkit-font-smoothing:antialiased} -@media(max-width:600px){body{font-size:13.5px}} +body{background:var(--bg);color:var(--text);font-family:var(--font-sans);line-height:1.8;font-size:17px;-webkit-font-smoothing:antialiased} +@media(max-width:600px){body{font-size:15px}} /* ── Header ── */ header{ @@ -206,6 +206,20 @@ footer .tagline{color:var(--accent2);margin-top:10px;font-weight:500} .quick-ref-grid{grid-template-columns:1fr} .badge-new{display:none} } + +/* ── Doc Tabs ── */ +.doc-tabs{display:flex;gap:0;margin-bottom:32px;border-bottom:1px solid var(--border);flex-wrap:wrap} +.doc-tabs input{display:none} +.doc-tabs label{ + flex-shrink:0;padding:12px 20px;cursor:pointer;font-size:.82em;font-weight:600; + color:var(--text2);border-bottom:2px solid transparent;transition:all .15s; + font-family:var(--font-sans);user-select:none;-webkit-user-select:none +} +.doc-tabs label:hover{color:var(--text)} +.doc-tabs input:checked+label{color:var(--accent2);border-color:var(--accent2)} +.doc-tab{display:none} +.doc-tab.active{display:block} + </style> </head> <body> @@ -250,19 +264,160 @@ footer .tagline{color:var(--accent2);margin-top:10px;font-weight:500} </nav> <main> +<div class="doc-tabs"> + <input type="radio" name="doc-layer" id="tab-basics" checked> + <label for="tab-basics">🌱 Start in 5 Min</label> + <input type="radio" name="doc-layer" id="tab-guide"> + <label for="tab-guide">📘 User Guide</label> + <input type="radio" name="doc-layer" id="tab-dev"> + <label for="tab-dev">🔧 Dev Reference</label> +</div> +<div class="doc-tab active" id="content-basics"> + <section id="basics-overview"> + <h2>🚀 Welcome to newOS4</h2> + <div class="callout callout-success"> + <span class="callout-title">🎮 New here?</span> + Type <code>tutorial</code> in the terminal to start a 2-minute interactive tour! + </div> + <div class="feature-grid"> + <div class="feature-card"><h4>🖥️ Terminal OS</h4><p>Full virtual filesystem with ls, cd, cat, touch, edit, find, grep and 50+ Unix-like commands.</p></div> + <div class="feature-card"><h4>🤖 AI Assistant</h4><p>Built-in AI agent with tool calling, skills, memory, and 28 tools. Just type <code>ai "your question"</code>.</p></div> + <div class="feature-card"><h4>🧩 Module System</h4><p>40+ modules including cron, mindmap, calculator, voice, WebDAV, themes, and shell scripting.</p></div> + <div class="feature-card"><h4>📚 Skills</h4><p>Self-improving AI remembers workflows and teaches you new capabilities — type <code>skill-list</code> to see what's installed.</p></div> + </div> + </section> + + <section id="basics-first"> + <h3>🔤 First 6 Commands</h3> + <div class="quick-ref"> + <div class="quick-ref-grid"> + <div class="quick-ref-item"><span class="cmd">help</span><span class="desc">Show all commands</span></div> + <div class="quick-ref-item"><span class="cmd">ls</span><span class="desc">List files</span></div> + <div class="quick-ref-item"><span class="cmd">cat</span><span class="desc">View file content</span></div> + <div class="quick-ref-item"><span class="cmd">clear</span><span class="desc">Clear terminal</span></div> + <div class="quick-ref-item"><span class="cmd">touch</span><span class="desc">Create a file</span></div> + <div class="quick-ref-item"><span class="cmd">ai</span><span class="desc">Ask the AI assistant</span></div> + </div> + </div> + <h3>📋 Workflow</h3> + <ol style="margin-bottom:14px;padding-left:22px;color:var(--text)"> + <li>Type <code>help</code> — see every available command</li> + <li>Type <code>ls</code> — browse files in your virtual drive</li> + <li>Type <code>touch hello.txt</code> — create a file</li> + <li>Type <code>edit hello.txt</code> — write something and save</li> + <li>Type <code>ai "summarize this"</code> — let AI analyze your files</li> + <li>Type <code>status</code> — check system health</li> + </ol> + </section> + + <section> + <h3>🔧 Quick Start</h3> + <p>Type <code>help</code> to see all 130+ commands grouped by category. Use <code>help <command></code> for details on a specific one.</p> + <p>The <strong>📋 Quick Commands</strong> panel (Ctrl+K) has clickable cards for common tasks.</p> + <p>For the full reference, switch to the <strong>📘 User Guide</strong> tab — it covers every module, filesystem, AI, skills, configuration, and all extended modules in detail.</p> + </section> +</div> <div class="search-box"> <input type="text" id="search-input" placeholder="Search docs…" spellcheck="false"> <button onclick="searchDocs()">Search</button> </div> <div class="search-result-msg" id="search-result-msg"></div> + <div class="doc-tab" id="content-dev"> + <section id="dev-api"> + <h2>🔧 Module Development API</h2> + <h3>Ext Module Structure</h3> + <p>Modules are ES6 classes with 3 required and 1 optional method:</p> + <pre><code>class MyModule { + constructor() { this.commands = {}; this.terminal = null; } + requirements() { return { met: true, missing: [] }; } + getHelpInfo() { return { 'mycmd': 'mycmd — What it does' }; } + registerCommands() { this.commands['mycmd'] = (args) => { /* ... */ }; } +} +// Auto-register: +if (typeof window.webx11 !== 'undefined' && window.webx11) { + webx11.moduleManager.registerModule(new MyModule(), 'url'); +}</code></pre> + + <h3>VFS (Virtual FileSystem) API</h3> + <p>Access via <code>this.terminal.fs</code>:</p> + <div class="table-wrap"><table> + <tr><th>Method</th><th>Returns</th><th>Description</th></tr> + <tr><td><code>readFile(path)</code></td><td>string or null</td><td>Read file content</td></tr> + <tr><td><code>writeFile(path, content)</code></td><td>boolean</td><td>Write string to file</td></tr> + <tr><td><code>ls(path)</code></td><td>array</td><td>List directory entries</td></tr> + <tr><td><code>mkdir(path)</code></td><td>boolean</td><td>Create directory</td></tr> + <tr><td><code>rm(path)</code></td><td>boolean</td><td>Remove file</td></tr> + <tr><td><code>rmdir(path)</code></td><td>boolean</td><td>Remove empty directory</td></tr> + <tr><td><code>touch(path)</code></td><td>boolean</td><td>Create empty file</td></tr> + <tr><td><code>cd(path)</code></td><td>boolean</td><td>Change current directory</td></tr> + <tr><td><code>pwd()</code></td><td>string</td><td>Current directory path</td></tr> + <tr><td><code>mv(old, new)</code></td><td>boolean</td><td>Move/rename file</td></tr> + <tr><td><code>cp(src, dst)</code></td><td>boolean</td><td>Copy file</td></tr> + </table></div> + + <h3>AI Tool Definition Format</h3> + <p>Add tools to <code>module-aiassist.js</code> in <code>getToolDefinitions()</code> (around line 300):</p> + <pre><code>{ + type: 'function', + function: { + name: 'my_tool', + description: 'What it does — AI reads this to decide when to call it.', + parameters: { type: 'object', properties: { + paramName: { type: 'string', description: 'What this param does' } + }, required: ['paramName'] } + } +}</code></pre> + <p>Then add a <code>case 'my_tool':</code> handler in <code>runTool()</code> (around line 1000). Return <code>JSON.stringify(...)</code>.</p> + </section> + + <section id="dev-skills"> + <h3>Skill Format</h3> + <p>Skills are markdown files with YAML frontmatter, stored in <code>/skills/</code> in VFS:</p> + <pre><code>--- +name: my-skill +description: Brief description +tags: [tag1, tag2] +--- +# Skill Body + +Instructions, examples, and workflows here.</code></pre> + </section> + + <section id="dev-ollama"> + <h3>OllamaManager API</h3> + <div class="table-wrap"><table> + <tr><th>Property/Method</th><th>Description</th></tr> + <tr><td><code>.endpoint</code></td><td>Current Ollama API URL</td></tr> + <tr><td><code>.currentModel</code></td><td>Active model name</td></tr> + <tr><td><code>.models</code></td><td>Array of available models</td></tr> + <tr><td><code>.connected</code></td><td>Boolean connection state</td></tr> + <tr><td><code>.connect()</code></td><td>Connect to Ollama, load models</td></tr> + <tr><td><code>.chatWithTools()</code></td><td>AI chat with tool calling support</td></tr> + </table></div> + </section> + + <section id="dev-global"> + <h3>window.webx11 Global</h3> + <p>The terminal instance is exposed globally as <code>window.webx11</code> (aliased as <code>webx11</code>). This gives access to everything:</p> + <pre><code>webx11.fs // VFS instance +webx11.moduleManager // Module registry +webx11.ollamaManager // Ollama connection +webx11.security // Security manager +webx11.executeCommand(cmd) // Run a terminal command programmatically +webx11.print(text, type) // Print to terminal output +webx11.tutorialModule // Tutorial progress (if active) +webx11.inputField // DOM input element</code></pre> + </section> +</div> +<div class="doc-tab" id="content-guide"> <!-- ── Overview ── --> <section id="overview"> <h2>⛅ Overview</h2> <p>WebX11 v4.2 is a browser-based Unix-like terminal — dynamic modules, AI assistant (Ollama), persistent virtual filesystem, web search, voice input, code editor, and a full skill system. Everything runs client-side in your browser.</p> <div class="feature-grid"> <div class="feature-card"><h4>🧩 26 Core + 14 Ext</h4><p>Modules loaded at startup. Shell with pipes, redirects, vars, and scripts.</p></div> - <div class="feature-card"><h4>🤖 Agent with Tools</h4><p>15+ tools — file ops, web search, skills, modules. Destructive action confirmations with inline bar + countdown.</p></div> + <div class="feature-card"><h4>🤖 Agent with Tools</h4><p>28 tools — file ops, web search, skills, modules. Destructive action confirmations with inline bar + countdown.</p></div> <div class="feature-card"><h4>📚 Skills System</h4><p>Persistent procedural memory. Create, patch, search, export skills for repeatable workflows.</p></div> <div class="feature-card"><h4>💾 VFS + Sessions</h4><p>Virtual filesystem persists in localStorage. Save/load/export sessions.</p></div> <div class="feature-card"><h4>🎤 Voice Input</h4><p>Speech recognition in the terminal. Works in Chrome, Edge, Safari.</p></div> @@ -441,14 +596,14 @@ diff a.txt b.txt</pre> <h2>🤖 AI Assistant</h2> <div class="callout callout-success"> <span class="callout-title">Tool-Calling Agent</span> - The AI runs a full agent loop with 15+ tools: filesystem, web search, URL fetch, skills, WPM, session memory, user profile, module introspection. + The AI runs a full agent loop with 28 tools: filesystem, web search, URL fetch, skills, WPM, session memory, user profile, module introspection. </div> <h3>How It Works</h3> <ol> <li>Type <code>ai <request></code></li> <li>AI loads relevant skills + recent session memory</li> - <li>Decides which tools to call (up to 5 turns)</li> + <li>Decides which tools to call (up to 8 turns)</li> <li><strong>Destructive tools</strong> trigger an inline confirm bar — no dark overlay</li> <li>120s countdown: yellow → bold red at 30s → pulsing at 10s</li> <li>Type <code>ai-allow</code> / <code>ai-deny</code> or tap the buttons</li> @@ -578,6 +733,7 @@ ollamaconnect</pre> <tr><td><code>ddg</code> / <code>duck</code></td><td>DDG</td><td>DuckDuckGo fallback</td><td><code>ddg news today</code></td></tr> <tr><td><code>fetch</code> / <code>web</code></td><td>Fetcher</td><td>Fetch URL content</td><td><code>fetch https://example.com</code></td></tr> <tr><td><code>extract</code></td><td>Fetcher</td><td>Clean readable text</td><td><code>extract https://news.article</code></td></tr> + <tr><td><code>images</code></td><td>SearXNG</td><td>Image search</td><td><code>images cats</code></td></tr> </table></div> <p>The AI agent also has <code>web_search</code> and <code>web_fetch</code> tools using the same APIs.</p> </section> @@ -931,6 +1087,10 @@ listmods</pre> </div> </section> + <div style="text-align:center;margin-bottom:32px;"> + <p>📄 One-page <a href="cheatsheet.html" style="color:var(--accent2);font-weight:600;">Quick Reference cheat sheet</a> — printable, dark mode, all commands.</p> + </div> + <!-- ── Changelog ── --> <section id="history"> <h2>📋 Changelog</h2> @@ -955,7 +1115,7 @@ listmods</pre> <li><strong>Diff</strong> — <code>diff a b</code> LCS-based comparison + <code>diff --module</code></li> <li><strong>Aliases</strong> — <code>ll</code>, <code>h</code>, <code>cls</code> defaults + custom alias definition</li> <li><strong>File Finder</strong> — <code>find <query></code> recursive VFS search</li> - <li>Agent loop: capped at 5 turns (was 1), <code>edit_module</code> destructive confirmation, model auto-validation</li> + <li>Agent loop: capped at 8 turns (was 1), <code>edit_module</code> destructive confirmation, model auto-validation</li> <li><code>ai-stop</code> now cancels chat + stream, not just tool calls</li> <li>Unknown commands awaited, "Command not found" suppressed on AI route</li> <li><code>loopResult</code> scope fix — no more undefined error on AI fail</li> @@ -967,7 +1127,7 @@ listmods</pre> <div class="version-item"> <div class="v-header"><span class="v-num">v4.1</span><span class="v-date">2026-05-23</span></div> <ul> - <li>AI Agent with full tool-calling loop (15+ tools)</li> + <li>AI Agent with full tool-calling loop (28 tools)</li> <li>Inline destructive confirmation bar with 120s countdown timer</li> <li>Skills system (8 CLI commands + UI modal)</li> <li>Voice input (🎤 button, SpeechRecognition, show/hide toggle)</li> @@ -997,6 +1157,7 @@ listmods</pre> </ul> </div> </section> +</div> </main> <footer> @@ -1020,15 +1181,29 @@ function searchDocs(){ const q=document.getElementById('search-input').value.toLowerCase().trim(); const msg=document.getElementById('search-result-msg'); if(!q){msg.style.display='none';return} - const sections=document.querySelectorAll('section'); - let found=false; - sections.forEach(s=>{ - if(s.textContent.toLowerCase().includes(q)){ - s.scrollIntoView({behavior:'smooth'}); - s.style.border='2px solid var(--accent)';s.style.borderRadius='12px'; - s.style.padding='0 20px';s.style.transition='border .3s'; - setTimeout(()=>{s.style.border='none';s.style.padding='0'},2000); - found=true; + // Search in ALL tabs, switch to the one with results + const tabs = document.querySelectorAll('.doc-tab'); + let found = false; + tabs.forEach(tab => { + if (tab.textContent.toLowerCase().includes(q)) { + // Found a match in this tab — switch to it + const tabId = tab.id.replace('content-', 'tab-'); + const radio = document.getElementById(tabId); + if (radio) { + radio.checked = true; + radio.dispatchEvent(new Event('change')); + } + // Highlight the matching element + const sections = tab.querySelectorAll('section, .quick-ref-item, .feature-card, .callout'); + sections.forEach(s => { + if (s.textContent.toLowerCase().includes(q)) { + s.scrollIntoView({behavior:'smooth', block:'center'}); + s.style.border='2px solid var(--accent)'; s.style.borderRadius='12px'; + s.style.padding='0 20px'; s.style.transition='border .3s'; + setTimeout(() => { s.style.border='none'; s.style.padding='0'; }, 2000); + found = true; + } + }); } }); if(!found){ @@ -1039,14 +1214,41 @@ function searchDocs(){ } document.getElementById('search-input').addEventListener('keypress',e=>{if(e.key==='Enter')searchDocs()}); -/* ── Smooth scroll nav ── */ +/* ── Smooth scroll nav — switch to User Guide tab first ── */ document.querySelectorAll('.nav-link').forEach(l=>{ l.addEventListener('click',e=>{ e.preventDefault(); - const t=document.querySelector(l.getAttribute('href')); - if(t)t.scrollIntoView({behavior:'smooth'}); + const href = l.getAttribute('href'); + // Switch to User Guide tab first + const guideTab = document.getElementById('tab-guide'); + if (guideTab) { + guideTab.checked = true; + guideTab.dispatchEvent(new Event('change')); + } + // Then scroll after a short delay so tab content becomes visible + setTimeout(() => { + const t = document.querySelector(href); + if (t) t.scrollIntoView({behavior:'smooth', block:'start'}); + }, 100); + }); +}); +/* ── Doc Tabs ── */ +document.querySelectorAll('.doc-tabs input').forEach(radio=>{ + radio.addEventListener('change',function(){ + document.querySelectorAll('.doc-tab').forEach(t=>t.classList.remove('active')); + const tab=document.getElementById('content-'+this.id.replace('tab-','')); + if(tab)tab.classList.add('active'); + localStorage.setItem('webx11_help_tab',this.id); }); }); +// Restore last tab +(function(){ + const saved = localStorage.getItem('webx11_help_tab'); + if(saved){ + const el = document.getElementById(saved); + if(el){ el.checked = true; el.dispatchEvent(new Event('change')); } + } +})(); console.log('WebX11 v4.2 Docs — Modern'); </script> @@ -567,6 +567,35 @@ .mod-card { padding: 10px 6px; } .mod-card .mod-name { font-size: 0.82em; } } + /* ── Command Cards Panel ── */ + #cmd-cards { + position: fixed; right: 0; top: 48px; bottom: 0; width: 280px; + background: var(--bg-secondary); border-left: 1px solid var(--border-color); + z-index: 999; display: none; flex-direction: column; + backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); + overflow-y: auto; + } + #cmd-cards.visible { display: flex; } + #cmd-cards-header { + display: flex; justify-content: space-between; align-items: center; + padding: 12px 14px; border-bottom: 1px solid var(--border-color); + font-weight: bold; font-size: 0.9em; + position: sticky; top: 0; background: var(--bg-secondary); + } + .cmd-card-section { padding: 10px 14px; } + .cmd-card-section + .cmd-card-section { border-top: 1px solid var(--border-color); } + .cmd-card-section-title { font-size: 0.75em; color: var(--text-muted); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 1px; } + .cmd-card { + display: flex; align-items: center; gap: 10px; + padding: 10px 12px; margin-bottom: 4px; + background: var(--bg-tertiary); border: 1px solid var(--border-color); + border-radius: 6px; cursor: pointer; transition: border-color .15s; + min-height: 44px; touch-action: manipulation; + } + .cmd-card:hover { border-color: var(--accent-color); } + .cmd-card .cmd { font-family: var(--font-primary); font-weight: bold; font-size: 0.85em; color: var(--accent-color); flex-shrink: 0; min-width: 60px; } + .cmd-card .desc { font-size: 0.78em; color: var(--text-muted); } + #cmd-cards-toggle { cursor: pointer; font-size: 1.1em; line-height: 1; } </style> </head> <body> @@ -576,10 +605,43 @@ <span class="header-version">v4.2</span> </div> <div id="header-status" class="status-pill">Mem: --</div> + <span id="cmd-cards-toggle" onclick="if(webx11)webx11.toggleCmdCards()" title="Quick Commands (Ctrl+K)">📋</span> </header> + <!-- Command Cards Panel --> + <div id="cmd-cards"> + <div id="cmd-cards-header"> + <span>📋 Quick Commands</span> + <span onclick="if(webx11)webx11.toggleCmdCards()" style="cursor:pointer;font-size:1.2em;">✕</span> + </div> + <div class="cmd-card-section"> + <div class="cmd-card-section-title">🟢 Beginners</div> + <div class="cmd-card" onclick="webx11?.executeCommand('help')"><span class="cmd">help</span><span class="desc">Show all commands</span></div> + <div class="cmd-card" onclick="webx11?.executeCommand('ls')"><span class="cmd">ls</span><span class="desc">List files</span></div> + <div class="cmd-card" onclick="webx11?.executeCommand('clear')"><span class="cmd">clear</span><span class="desc">Clear terminal</span></div> + <div class="cmd-card" onclick="webx11?.executeCommand('status')"><span class="cmd">status</span><span class="desc">System health</span></div> + </div> + <div class="cmd-card-section"> + <div class="cmd-card-section-title">🤖 AI</div> + <div class="cmd-card" onclick="webx11?.executeCommand('ai \"what can you do?\"')"><span class="cmd">ai "..."</span><span class="desc">Ask AI assistant</span></div> + <div class="cmd-card" onclick="webx11?.executeCommand('ai-model')"><span class="cmd">ai-model</span><span class="desc">Set AI model</span></div> + </div> + <div class="cmd-card-section"> + <div class="cmd-card-section-title">🔍 Explore</div> + <div class="cmd-card" onclick="webx11?.executeCommand('modules')"><span class="cmd">modules</span><span class="desc">List loaded modules</span></div> + <div class="cmd-card" onclick="webx11?.executeCommand('tutorial')"><span class="cmd">tutorial</span><span class="desc">Interactive tour</span></div> + </div> + <div class="cmd-card-section"> + <div class="cmd-card-section-title">🚀 Advanced</div> + <div class="cmd-card" onclick="webx11?.executeCommand('cron-every 60 echo hello')"><span class="cmd">cron</span><span class="desc">Schedule tasks</span></div> + <div class="cmd-card" onclick="webx11?.executeCommand('webdav-status')"><span class="cmd">webdav</span><span class="desc">Cloud sync</span></div> + <div class="cmd-card" onclick="webx11?.executeCommand('help.html')"><span class="cmd">help</span><span class="desc">Full reference</span></div> + </div> + </div> + + <!-- Terminal Container --> <div id="terminal-container"> -<div class="output-line info">WebX11 Module System v4.2 - Unix-like Terminal</div> + <div class="output-line info">WebX11 Module System v4.2 - Unix-like Terminal</div> <div class="output-line info">Commands: ls, cd, pwd, cat, echo, rm, mkdir, mv, cp, touch, tree, find, grep</div> <div class="output-line info">Type 'help' for all commands</div> <div class="output-line"></div> @@ -708,10 +770,11 @@ <span style="color:var(--warning-color);">Disable on slow connections or when browser becomes unresponsive during long outputs.</span> </div> <hr style="border-color:var(--border-color); margin: 15px 0;"> - <h4 style="color: var(--info-color); margin-bottom: 10px;">🔍 Validator Agent (Future)</h4> + <h4 style="color: var(--info-color); margin-bottom: 10px;">🔍 Validator Agent</h4> <div style="font-size:0.85em; color:var(--text-muted); margin-bottom: 10px;"> A separate lightweight AI that validates the main agent's tool results. Has its own - endpoint and model — independent from your active AI model. + endpoint and model — independent from your active AI model.<br> + <span style="color:var(--success-color);">⚡ Best validator model: <strong>qwen2.5:0.5b</strong> (1.94s, 352MB)</span> </div> <div style="display:flex; align-items:center; gap:10px; margin-bottom: 10px;"> <span style="color:var(--text-muted); font-size:0.85em;">Enable Validator:</span> @@ -862,6 +925,10 @@ Skip — I'll explore </button> </div> + <label style="display:flex;align-items:center;justify-content:center;gap:6px;margin-top:14px;font-size:0.78em;color:var(--text-muted);cursor:pointer;"> + <input type="checkbox" id="welcome-dont-show" onchange="if(this.checked)localStorage.setItem('webx11_welcome_dismissed','1');else localStorage.removeItem('webx11_welcome_dismissed')"> + Don't show this again + </label> <p style="font-size:0.78em;color:var(--text-muted);margin-top:16px;"> Your progress is saved. Type <code>tutorial</code> anytime to resume. </p> diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index 8f8a60c..f77ec61 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -665,17 +665,19 @@ RULES: } // Validator: check safe tool results for consistency if (safeResults.length > 0 && pendingText) { - const om = this.terminal.ollamaManager; + const om = this.terminal.ollamaManager; if (om && om.validatorEnabled && om.validatorModel) { - let anyInvalid = false; + let invalidTools = []; for (const { fn, toolResult } of safeResults) { const valid = await om.validateToolResult(fn.name, fn.arguments, toolResult, pendingText || ''); if (!valid && !this._cancelled) { - anyInvalid = true; + invalidTools.push(fn.name); this.terminal.print('🔍 Validator: ' + fn.name + ' result — AI note may not match (logged)', 'info'); - // Just log it — don't force retry (cloud models get 400 on retry) } } + if (invalidTools.length > 0) { + pendingText += '\n\n⚠️ **Validator flagged** ' + invalidTools.join(', ') + ' — the AI analysis may not match what was returned. Use `ai-ignore` to dismiss and proceed.'; + } } } if (pendingText) { diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index cc3a79c..667a396 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -156,7 +156,7 @@ // Phase 4: Ext modules (filtered) if (typeof webx11._loadExtModules === 'function') { - const extKeys = ['calc', 'voice', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'tutorial']; + const extKeys = ['calc', 'voice', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'tutorial', 'shell', 'busybox']; const extSelected = selected ? extKeys.filter(k => isSelected(k + '.js', selected)) : extKeys; diff --git a/modules/core/ollama-manager.js b/modules/core/ollama-manager.js index ab3ec2c..9999184 100644 --- a/modules/core/ollama-manager.js +++ b/modules/core/ollama-manager.js @@ -15,9 +15,9 @@ class OllamaManager { this.optimizationEnabled = localStorage.getItem('webx11_optimization_enabled') !== 'false'; // --- Streaming Toggle --- this.streamEnabled = localStorage.getItem('webx11_stream_enabled') !== 'false'; - // --- Validator Agent Config (future) --- - this.validatorEnabled = localStorage.getItem('webx11_validator_enabled') === 'true'; - this.validatorModel = localStorage.getItem('webx11_validator_model') || ''; + // --- Validator Agent Config --- + this.validatorEnabled = localStorage.getItem('webx11_validator_enabled') !== 'false'; + this.validatorModel = localStorage.getItem('webx11_validator_model') || 'ministral-3:3b-cloud'; this.validatorEndpoint = localStorage.getItem('webx11_validator_endpoint') || ''; // Build fallback endpoints this.fallbackEndpoints = []; @@ -865,6 +865,70 @@ class OllamaManager { } } + // MiniCPM5 XML tool calling mode — sends tools in message context instead of API parameter + // Model natively outputs <function name="x"><param name="y">val</param> XML + if (useModel && useModel.includes('minicpm5') && tools && tools.length > 0) { + this.terminal.print(`🧩 Using XML tool mode for ${useModel}...`, 'info'); + try { + // Inject tool defs into a system message (the orchestrator model knows XML format) + const toolDesc = tools.map(t => JSON.stringify({type:'function',function:t.function})).join('\n'); + const toolContext = { role: 'system', content: +`You are a tool orchestrator. When you need to call a tool, output XML like: +<function name="function-name"><param name="param-name">param-value</param> + +Available tools: +<tools> +${toolDesc} +</tools> + +Only output XML function calls for tool tasks. If no tool is needed, answer normally.` }; + const msgsWithTools = [toolContext, ...messages]; + const body = { model: useModel, messages: msgsWithTools, stream: false, options: { temperature: 0.1 } }; + + const response = await fetch(`${this.endpoint}/api/chat`, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), signal + }); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const data = await response.json(); + const text = (data.message?.content || '').trim(); + + // Parse XML function calls from the response + const toolCalls = []; + const funcRegex = /<function\s+name="([^"]+)"(.*?)<\/function>/gs; + let match; + while ((match = funcRegex.exec(text)) !== null) { + const funcName = match[1]; + const args = {}; + const paramRegex = /<param\s+name="([^"]+)"(.*?)>(.*?)<\/param>/gs; + let pmatch; + while ((pmatch = paramRegex.exec(match[0])) !== null) { + args[pmatch[1]] = pmatch[3] + .replace('<![CDATA[', '').replace(']]>', '') + .replace(/^\s+|\s+$/g, ''); + } + if (funcName && Object.keys(args).length > 0) { + toolCalls.push({ + id: 'call_' + Math.random().toString(36).slice(2, 10), + type: 'function', + function: { name: funcName, arguments: JSON.stringify(args) } + }); + } + } + if (toolCalls.length > 0) { + this.terminal.print(`✅ XML tool call(s) parsed from ${useModel}`, 'success'); + return { message: { role: 'assistant', content: '' }, tool_calls: toolCalls, error: null }; + } + + return { message: { role: 'assistant', content: text || '(no response)' }, tool_calls: null, error: null }; + } catch (e) { + if (e.name === 'AbortError') { + return { message: null, tool_calls: null, error: 'Cancelled' }; + } + return { message: null, tool_calls: null, error: e.message }; + } + } + // Original Ollama fallback this.terminal.print(`Model doesn't support tool calling. Retrying /api/chat without tools...`, 'warning'); try { @@ -981,7 +1045,7 @@ class OllamaManager { }); const data = await res.json(); const answer = (data.message?.content || '').trim().toUpperCase(); - return answer === 'YES'; + return answer.startsWith('YES'); } catch (e) { if (this.terminal) this.terminal.print('⚠️ Validator error: ' + e.message, 'warning'); return true; diff --git a/modules/core/terminal.js b/modules/core/terminal.js index d53ab70..5008842 100644 --- a/modules/core/terminal.js +++ b/modules/core/terminal.js @@ -54,13 +54,12 @@ let curr = new Uint16Array(n + 1); for (let j = 0; j <= n; j++) prev[j] = j; for (let i = 1; i <= m; i++) { - let next = new Uint16Array(n + 1); - next[0] = i; + curr[0] = i; for (let j = 1; j <= n; j++) { const cost = a[i - 1] === b[j - 1] ? 0 : 1; - next[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost); + curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost); } - [prev, curr] = [curr, next]; + [prev, curr] = [curr, prev]; } return prev[n]; } @@ -98,8 +97,11 @@ if (['help','security','security-level'].includes(cmd)) { return this._dispatch(cmd, args); } - // Unknown command handling + // Unknown command handling — skip at level 0 (permissive) if (unknown && !this.whitelist.has(cmd)) { + if (this.level === 0) { + return this._dispatch(cmd, args); + } this.blockedCount++; const sug = this.suggest(cmd); if (sug) { @@ -107,7 +109,7 @@ } else { this.terminal.print(`Security: Unknown command "${cmd}" — blocked. Use 'security-level 0' to disable.`, 'warning'); } - // At levels 0-1: suggest/fix but still route to AI (terminal is also a chat interface) + // At level 1: suggest/fix but still route to AI (terminal is also a chat interface) // At level 2+: block entirely (paranoid) if (this.level >= 2) { this.terminal.print(`Security: Unknown command "${cmd}" blocked (level ${this.level}).`, 'error'); @@ -272,6 +274,9 @@ this.selectedSuggestion = (this.selectedSuggestion + 1) % this.suggestions.length; this.updateAutocompleteDisplay(); } + } else if (e.ctrlKey && (e.key === 'k' || e.key === 'K')) { + e.preventDefault(); + this.toggleCmdCards(); } else if (e.key === 'Escape') { if (this._aiBusy) { this.executeCommand('ai-stop'); @@ -984,7 +989,7 @@ catch (e) { console.warn(`[SafeRegister] Skipped ${label}: ${e.message}`); } } async _loadExtModules(filterKeys) { - const autoLoadKeys = filterKeys || ['calc', 'voice', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'shell', 'tutorial']; + const autoLoadKeys = filterKeys || ['calc', 'voice', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'shell', 'tutorial', 'busybox']; const cacheVer = localStorage.getItem('webx11_cache_ver') || '2026-05-27-14'; try { const res = await fetch('packages.json?v=' + cacheVer); @@ -1098,7 +1103,7 @@ } // Enhanced: check fuzzy suggestions before showing "not found" const suggestion = this.security.suggest(cmd); - if (suggestion && suggestion.dist <= 2) { + if (suggestion && suggestion.dist <= 2 && suggestion.dist > 0) { this.print(`Command not found: ${cmd}`, 'error'); this.print(`Did you mean: ${suggestion.cmd}? (yes) (no)`, 'info'); // Render clickable yes/no @@ -1111,7 +1116,7 @@ const noSpan = document.createElement('span'); noSpan.textContent = ' [no] '; noSpan.style.cssText = 'color:var(--error-color);cursor:pointer;text-decoration:underline'; - noSpan.onclick = () => { /* do nothing — user declined */ }; + noSpan.onclick = () => { this.print('Dismissed.', 'text-muted'); }; const line = out.lastElementChild; if (line) { line.appendChild(yesSpan); line.appendChild(noSpan); } } @@ -1450,8 +1455,7 @@ const endpoint = document.getElementById('ollama-endpoint').value.trim(); this.ollamaManager.setEndpoint(endpoint); const success = await this.ollamaManager.connect(); - this.updateOllamaStatus(); - if (success) { this.renderOllamaModels(); } + if (success) { this.renderOllamaModels(); this._populateValidatorModels(); this.updateOllamaStatus(); } } setOllamaModel() { const select = document.getElementById('ollama-current-model'); @@ -1589,6 +1593,8 @@ valSlider.style.borderColor = enabled ? 'var(--info-color)' : 'var(--border-color)'; } } + // Auto-populate validator model dropdown from connected models + this._populateValidatorModels(); // Initialize stream toggle from stored state const strCb = document.getElementById('ollama-stream-toggle'); const strLabel = document.getElementById('ollama-stream-label'); @@ -1617,12 +1623,27 @@ if (storedModel) { valSelect.innerHTML = '<option value="' + storedModel + '">' + storedModel + '</option><option value="">Disable (use main AI model)</option>'; valSelect.disabled = false; - } else { - valSelect.innerHTML = '<option value="">Connect to load models</option>'; - valSelect.disabled = true; } } } + _populateValidatorModels() { + const select = document.getElementById('validator-model-select'); + if (!select || !this.ollamaManager) return; + const models = this.ollamaManager.getModels(); + if (!models || models.length === 0) { + select.innerHTML = '<option value="">Connect to load models</option>'; + select.disabled = true; + return; + } + const stored = this.ollamaManager.validatorModel || ''; + let html = '<option value="">Disable (use main AI model)</option>'; + models.forEach(function(m) { + const sel = m.name === stored ? ' selected' : ''; + html += '<option value="' + m.name + '"' + sel + '>' + m.name + '</option>'; + }); + select.innerHTML = html; + select.disabled = false; + } openStartupModules() { document.getElementById('startup-modules').style.display = 'flex'; this.renderStartupModules(); @@ -1811,6 +1832,15 @@ // Also mark tutorial as skipped so it doesn't reappear if (this.tutorialModule) this.tutorialModule.skipTutorial(); } + toggleCmdCards() { + const panel = document.getElementById('cmd-cards'); + if (!panel) return; + panel.classList.toggle('visible'); + // If opening, focus the input field + if (panel.classList.contains('visible')) { + setTimeout(() => this.inputField.focus(), 50); + } + } log(level, source, message) { const d = new Date(); const ts = d.toISOString().split('T')[1].split('.')[0]; diff --git a/modules/ext/busybox.js b/modules/ext/busybox.js index 709324f..34221f8 100644 --- a/modules/ext/busybox.js +++ b/modules/ext/busybox.js @@ -57,8 +57,12 @@ class BusyBoxModule { const keys = Object.keys(all); if (all[query]) { - const mod = this.terminal.moduleManager._findModuleForCommand(query); - const src = mod ? ` (from ${mod.constructor.name})` : ''; + let foundMod = null; + for (const modName in this.terminal.moduleManager.modules) { + const mod = this.terminal.moduleManager.modules[modName]; + if (mod.commands && mod.commands[query]) { foundMod = mod; break; } + } + const src = foundMod ? ` (from ${foundMod.constructor.name})` : ''; return this.terminal.print(`${query}${src}`, 'info'); } |