From cf7461ffeb4f53f3f50c9c692a2e4fdcecf17e82 Mon Sep 17 00:00:00 2001 From: Agando Date: Thu, 9 Jul 2026 06:00:32 +0200 Subject: auto: 2026-07-09_06:00 changes --- modules/core/terminal.js | 291 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 233 insertions(+), 58 deletions(-) (limited to 'modules/core/terminal.js') diff --git a/modules/core/terminal.js b/modules/core/terminal.js index 3abb074..9896bee 100644 --- a/modules/core/terminal.js +++ b/modules/core/terminal.js @@ -7,19 +7,71 @@ this.lastRouteTime = 0; this.lastRouteCmd = ''; this.blockedCount = 0; - 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','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){} - try { const w = localStorage.getItem('webx11_security_whitelist'); if(w){JSON.parse(w).forEach(c=>this.whitelist.add(c));} }catch(e){} - try { const f = localStorage.getItem('webx11_security_flags'); if(f){JSON.parse(f).forEach(c=>this.suspiciousFlags.add(c));} }catch(e){} + this._baseDestructive = ['delete','del','write','save','overwrite','format','kill','stop','shutdown','reboot']; + this._baseSystem = ['reboot','shutdown','format','kill','halt','poweroff']; + this._baseSuspiciousFlags = ['--dump','--execute','--eval','--exec','--shell','--script']; + this._baseWhitelist = ['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','debug-stats','debug-history','debug-analyze','debug-trends','debug-fix','debug-health','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','dashboard','context-auto']; + this.destructiveCmds = new Set(this._baseDestructive); + this.systemCmds = new Set(this._baseSystem); + this.suspiciousFlags = new Set(this._baseSuspiciousFlags); + this.whitelist = new Set(this._baseWhitelist); + // Load persisted user deltas (add/remove) so hardcoded defaults can be demoted. + this._loadSecurityDeltas(); this._registerBuiltIn(); } + _loadSecurityDeltas() { + try { + const schema = this.terminal._getDataSchemaVersion ? this.terminal._getDataSchemaVersion() : 1; + localStorage.setItem('webx11_data_schema_version', String(schema)); + const raw = localStorage.getItem('webx11_security_deltas'); + if (!raw) { + // One-time migration from legacy flat keys to delta format. + const legacy = {}; + try { legacy.destructive = JSON.parse(localStorage.getItem('webx11_security_destructive') || '[]'); } catch (e) {} + try { legacy.system = JSON.parse(localStorage.getItem('webx11_security_system') || '[]'); } catch (e) {} + try { legacy.whitelist = JSON.parse(localStorage.getItem('webx11_security_whitelist') || '[]'); } catch (e) {} + try { legacy.flags = JSON.parse(localStorage.getItem('webx11_security_flags') || '[]'); } catch (e) {} + const delta = { add: {}, remove: {} }; + for (const c of (legacy.destructive || [])) if (!this._baseDestructive.includes(c)) { if (!delta.add.destructive) delta.add.destructive = []; delta.add.destructive.push(c); } + for (const c of (legacy.system || [])) if (!this._baseSystem.includes(c)) { if (!delta.add.system) delta.add.system = []; delta.add.system.push(c); } + for (const c of (legacy.whitelist || [])) if (!this._baseWhitelist.includes(c)) { if (!delta.add.whitelist) delta.add.whitelist = []; delta.add.whitelist.push(c); } + for (const f of (legacy.flags || [])) if (!this._baseSuspiciousFlags.includes(f)) { if (!delta.add.flags) delta.add.flags = []; delta.add.flags.push(f); } + localStorage.setItem('webx11_security_deltas', JSON.stringify(delta)); + localStorage.removeItem('webx11_security_destructive'); + localStorage.removeItem('webx11_security_system'); + localStorage.removeItem('webx11_security_whitelist'); + localStorage.removeItem('webx11_security_flags'); + return; + } + const d = JSON.parse(raw); + if (d.remove) { + for (const c of (d.remove.destructive || [])) this.destructiveCmds.delete(c); + for (const c of (d.remove.system || [])) this.systemCmds.delete(c); + for (const c of (d.remove.whitelist || [])) this.whitelist.delete(c); + for (const f of (d.remove.flags || [])) this.suspiciousFlags.delete(f); + } + if (d.add) { + for (const c of (d.add.destructive || [])) this.destructiveCmds.add(c); + for (const c of (d.add.system || [])) this.systemCmds.add(c); + for (const c of (d.add.whitelist || [])) this.whitelist.add(c); + for (const f of (d.add.flags || [])) this.suspiciousFlags.add(f); + } + } catch (e) {} + } + _saveSecurityDeltas() { + const delta = { add: {}, remove: {} }; + for (const c of this.destructiveCmds) if (!this._baseDestructive.includes(c)) { if (!delta.add.destructive) delta.add.destructive = []; delta.add.destructive.push(c); } + for (const c of this._baseDestructive) if (!this.destructiveCmds.has(c)) { if (!delta.remove.destructive) delta.remove.destructive = []; delta.remove.destructive.push(c); } + for (const c of this.systemCmds) if (!this._baseSystem.includes(c)) { if (!delta.add.system) delta.add.system = []; delta.add.system.push(c); } + for (const c of this._baseSystem) if (!this.systemCmds.has(c)) { if (!delta.remove.system) delta.remove.system = []; delta.remove.system.push(c); } + for (const c of this.whitelist) if (!this._baseWhitelist.includes(c)) { if (!delta.add.whitelist) delta.add.whitelist = []; delta.add.whitelist.push(c); } + for (const c of this._baseWhitelist) if (!this.whitelist.has(c)) { if (!delta.remove.whitelist) delta.remove.whitelist = []; delta.remove.whitelist.push(c); } + for (const f of this.suspiciousFlags) if (!this._baseSuspiciousFlags.includes(f)) { if (!delta.add.flags) delta.add.flags = []; delta.add.flags.push(f); } + for (const f of this._baseSuspiciousFlags) if (!this.suspiciousFlags.has(f)) { if (!delta.remove.flags) delta.remove.flags = []; delta.remove.flags.push(f); } + localStorage.setItem('webx11_security_deltas', JSON.stringify(delta)); + } _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','voice','speak','analyze']; + const internals = ['clear','help','ls','cat','df','free','env','history','cd','pwd','touch','mkdir','rm','cp','mv','edit','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','dashboard']; internals.forEach(c => this.registerCommand(c, { category: 'builtin' })); } setLevel(n) { @@ -38,8 +90,13 @@ } registerCommand(name, meta = {}) { this.commandMeta.set(name, { destructive: false, category: 'user', ...meta }); + this.invalidateCommandCache(); + } + invalidateCommandCache() { + this._commandCacheDirty = true; } _collectAllCommands() { + if (!this._commandCacheDirty && this._commandCache) return this._commandCache; const cmds = new Set([...this.commandMeta.keys()]); for (const modName in this.terminal.moduleManager.modules) { const mod = this.terminal.moduleManager.modules[modName]; @@ -49,7 +106,9 @@ } const builtins = ['clear','help','ls','df','security','security-level']; builtins.forEach(c => cmds.add(c)); - return [...cmds]; + this._commandCache = [...cmds]; + this._commandCacheDirty = false; + return this._commandCache; } _levenshtein(a, b) { a = a.toLowerCase(); b = b.toLowerCase(); @@ -278,6 +337,19 @@ // Apply config overrides after everything is initialized this._applyTerminalConfig(); } + /* ---------- helpers ---------- */ + static _escapeHtml(str) { + if (str == null) return ''; + return String(str).replace(/[&<>'"]/g, m => ({'&':'&','<':'<','>':'>',"'":''','"':'"'}[m])); + } + _escapeHtml(str) { return WebX11Terminal._escapeHtml(str); } + _getDataSchemaVersion() { return 1; } + _escapeYamlString(str) { + return '"' + String(str == null ? '' : str).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'; + } + _invalidateCommandCache() { + if (this.security) this.security.invalidateCommandCache(); + } initEventListeners() { this.inputField.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { @@ -1195,6 +1267,7 @@ this._safeRegister('ServerMonitorModule', 'server monitor'); this._safeRegister('SmartContextModule', 'smart context'); this._safeRegister('SimpleGUIModule', 'simple GUI builder'); + this._safeRegister('DebugSupervisorModule', 'debug supervisor'); // v4.5.47: Critical ext modules promoted to boot path this._safeRegister('EditorModule', 'editor'); this._safeRegister('ShellModule', 'shell'); @@ -1242,6 +1315,7 @@ DiffModule: typeof DiffModule !== 'undefined' ? DiffModule : undefined, BusyBoxModule: typeof BusyBoxModule !== 'undefined' ? BusyBoxModule : undefined, SmartContextModule: typeof SmartContextModule !== 'undefined' ? SmartContextModule : undefined, + DebugSupervisorModule: typeof DebugSupervisorModule !== 'undefined' ? DebugSupervisorModule : undefined, }; const cls = registry[className]; if (typeof cls === 'undefined') { @@ -1371,10 +1445,12 @@ splitPipe(input) { const parts = []; let current = ''; - let inQuote = false; + let inSingle = false; + let inDouble = false; for (const ch of input) { - if (ch === '"' || ch === "'") { inQuote = !inQuote; current += ch; } - else if (ch === '|' && !inQuote) { parts.push(current.trim()); current = ''; } + if (ch === '"' && !inSingle) { inDouble = !inDouble; current += ch; } + else if (ch === "'" && !inDouble) { inSingle = !inSingle; current += ch; } + else if (ch === '|' && !inSingle && !inDouble) { parts.push(current.trim()); current = ''; } else { current += ch; } } parts.push(current.trim()); @@ -1414,8 +1490,11 @@ const originalPrint = this.print.bind(this); let output = ''; this.print = (text, type) => { output += text + '\n'; }; - await this.dispatchCommand(cmd, args); - this.print = originalPrint; + try { + await this.dispatchCommand(cmd, args); + } finally { + this.print = originalPrint; + } const trimmed = output.trim(); if (trimmed.length > this._pipeBufferSize) { return trimmed.slice(0, this._pipeBufferSize); @@ -1431,6 +1510,21 @@ if (cmd === 'load-local' || cmd === 'loadlocal') { this.loadLocalModule(args[0]); return; } if (cmd === 'smart-prompt') { const val = args[0] && args[0] === 'off' ? 'off' : 'on'; localStorage.setItem('webx11_smart_prompt', val); this.print('Smart prompt ' + val, 'success'); this.updatePrompt(); return; } if (cmd === 'mem') { this.printMemoryStats(); return; } + if (cmd === 'dashboard') { + // Collect dashboard data and open the dashboard page + const aiMod = this.moduleManager?.modules?.WebX11AIAssistantModule; + if (aiMod && typeof aiMod._collectDashboardData === 'function') { + aiMod._collectDashboardData().then(function() { + this.print('📊 Dashboard data collected — opening dashboard...', 'success'); + window.open('/spaces/newos4/downloads/dashboard.html', 'newOS4 Dashboard', 'width=1200,height=800'); + }.bind(this)); + } else { + // Fallback: just open the page (it will try localStorage) + this.print('📊 Opening dashboard...', 'info'); + window.open('/spaces/newos4/downloads/dashboard.html', 'newOS4 Dashboard', 'width=1200,height=800'); + } + return; + } if (cmd === 'config') { if (args.length === 0) { this.print('=== Terminal Config ===', 'info'); @@ -1493,7 +1587,7 @@ if (action === 'add') { this.security.destructiveCmds.add(args[1]); } else if (action === 'remove') { this.security.destructiveCmds.delete(args[1]); } else { this.print('Action must be: add, remove, or list', 'warning'); return; } - localStorage.setItem('webx11_security_destructive', JSON.stringify([...this.security.destructiveCmds])); + this.security._saveSecurityDeltas(); this.print(`Updated destructive set (${this.security.destructiveCmds.size} commands)`, 'success'); return; } @@ -1509,7 +1603,7 @@ if (action === 'add') { this.security.systemCmds.add(args[1]); } else if (action === 'remove') { this.security.systemCmds.delete(args[1]); } else { this.print('Action must be: add, remove, or list', 'warning'); return; } - localStorage.setItem('webx11_security_system', JSON.stringify([...this.security.systemCmds])); + this.security._saveSecurityDeltas(); this.print(`Updated system set (${this.security.systemCmds.size} commands)`, 'success'); return; } @@ -1525,7 +1619,7 @@ if (action === 'add') { this.security.whitelist.add(args[1]); } else if (action === 'remove') { this.security.whitelist.delete(args[1]); } else { this.print('Action must be: add, remove, or list', 'warning'); return; } - localStorage.setItem('webx11_security_whitelist', JSON.stringify([...this.security.whitelist])); + this.security._saveSecurityDeltas(); this.print(`Updated whitelist (${this.security.whitelist.size} commands)`, 'success'); return; } @@ -1542,15 +1636,15 @@ if (action === 'add') { this.security.suspiciousFlags.add(flag); } else if (action === 'remove') { this.security.suspiciousFlags.delete(flag); } else { this.print('Action must be: add, remove, or list', 'warning'); return; } - localStorage.setItem('webx11_security_flags', JSON.stringify([...this.security.suspiciousFlags])); + this.security._saveSecurityDeltas(); this.print(`Updated suspicious flags (${this.security.suspiciousFlags.size} flags)`, 'success'); return; } if (cmd === 'cmd-allow') { this._resolveCmdConfirm(true); return; } if (cmd === 'cmd-deny') { this._resolveCmdConfirm(false); return; } if (cmd === 'vfs' && args[0] === 'reset') { - if (args[1] !== '--force') { - this.print('⚠ This will permanently delete all files! Use vfs reset --force to confirm.', 'warning'); + if (args[1] !== '--confirm' && args[1] !== '--force') { + this.print('⚠ This will permanently delete all files! Use vfs reset --confirm to confirm.', 'warning'); return; } this.fs.reset(); @@ -1592,8 +1686,8 @@ return; } if (cmd === 'empty-trash') { - if (args[0] !== '--force') { - this.print('⚠ This will permanently delete all trashed files! Use empty-trash --force to confirm.', 'warning'); + if (args[0] !== '--confirm' && args[0] !== '--force') { + this.print('⚠ This will permanently delete all trashed files! Use empty-trash --confirm to confirm.', 'warning'); return; } if (this.fs.emptyTrash()) { @@ -1710,7 +1804,11 @@ } catch (_e) {} } return; } - catch (err) { this.print(`Error in ${modName}.${cmd}: ${err.message}`, 'error'); return; } + catch (err) { + this.print(`Error in ${modName}.${cmd}: ${err.message}`, 'error'); + console.error(`[Terminal] Error in ${modName}.${cmd}:`, err); + return; + } } } // Enhanced: check fuzzy suggestions before showing "not found" @@ -1747,7 +1845,17 @@ files.forEach(f => { const div = document.createElement('div'); div.className = `file-item ${f.type === 'dir' ? 'dir' : ''}`; - div.innerHTML = `${f.type === 'dir' ? '📁' : '📄'} ${f.name} ${f.size}b`; + const icon = document.createElement('span'); + icon.textContent = f.type === 'dir' ? '📁' : '📄'; + const name = document.createElement('span'); + name.textContent = ' ' + f.name; + const size = document.createElement('span'); + size.style.fontSize = '0.8em'; + size.textContent = ' ' + f.size + 'b'; + div.appendChild(icon); + div.appendChild(name); + div.appendChild(document.createTextNode(' ')); + div.appendChild(size); div.onclick = () => { if (f.type === 'file') { const content = this.fs.readFile(f.name); @@ -1786,14 +1894,29 @@ modules.forEach(mod => { const div = document.createElement('div'); div.className = `module-item ${mod.loaded ? '' : 'disabled'}`; - const badge = mod.url ? '' : 'Built-in'; - div.innerHTML = ` -
- ${mod.name}${badge} -
${mod.url || 'Built-in'}
-
- ${mod.url ? `` : ''} - `; + const info = document.createElement('div'); + const strong = document.createElement('strong'); + strong.textContent = mod.name; + info.appendChild(strong); + if (!mod.url) { + const badge = document.createElement('span'); + badge.className = 'module-badge builtin'; + badge.textContent = 'Built-in'; + info.appendChild(badge); + } + const sub = document.createElement('div'); + sub.style.cssText = 'font-size:0.8em; color:#666'; + sub.textContent = mod.url || 'Built-in'; + info.appendChild(sub); + div.appendChild(info); + if (mod.url) { + const btn = document.createElement('button'); + btn.className = 'btn btn-danger'; + btn.style.padding = '4px 8px'; + btn.textContent = '✕'; + btn.onclick = () => this.removeModule(mod.name); + div.appendChild(btn); + } list.appendChild(div); }); } @@ -1905,6 +2028,7 @@ renderConfigList() { const list = document.getElementById('config-list'); if (!list) return; + list.innerHTML = ''; const prefix = 'webx11_config_'; const entries = []; for (let i = 0; i < localStorage.length; i++) { @@ -1917,14 +2041,28 @@ list.innerHTML = '
No configuration saved.
'; return; } - list.innerHTML = entries.map(e => { - const displayVal = e.key.toLowerCase().includes('password') || e.key.toLowerCase().includes('token') || e.key.toLowerCase().includes('secret') - ? '****' : e.value; - return `
-
${e.key} ${displayVal}
- -
`; - }).join(''); + entries.forEach(e => { + const item = document.createElement('div'); + item.className = 'module-item'; + const info = document.createElement('div'); + const strong = document.createElement('strong'); + strong.textContent = e.key; + info.appendChild(strong); + const val = document.createElement('span'); + val.style.color = 'var(--text-muted)'; + const sensitive = /password|token|secret|key|private|credential|auth/i.test(e.key); + val.textContent = sensitive ? '****' : e.value; + info.appendChild(document.createTextNode(' ')); + info.appendChild(val); + const btn = document.createElement('button'); + btn.className = 'btn btn-danger'; + btn.style.cssText = 'padding:4px 8px; font-size:0.8em'; + btn.textContent = '✕'; + btn.onclick = () => this.deleteConfigFromUI(e.key); + item.appendChild(info); + item.appendChild(btn); + list.appendChild(item); + }); } addConfigFromUI() { const key = document.getElementById('config-key-input').value.trim(); @@ -1977,10 +2115,24 @@ // Stats if (statsEl) { const cats = new Set(Object.values(index).map(s => s.category || 'general')); - statsEl.innerHTML = `📚 ${names.length} skills📂 ${cats.size} categories${query ? `🔍 ${filtered.length} match '${query}'` : ''}`; + statsEl.innerHTML = ''; + const totalSpan = document.createElement('span'); + totalSpan.textContent = '📚 ' + names.length + ' skills'; + statsEl.appendChild(totalSpan); + const catSpan = document.createElement('span'); + catSpan.textContent = '📂 ' + cats.size + ' categories'; + statsEl.appendChild(catSpan); + if (query) { + const matchSpan = document.createElement('span'); + matchSpan.textContent = '🔍 ' + filtered.length + ' match'; + statsEl.appendChild(matchSpan); + } } if (filtered.length === 0) { - list.innerHTML = '
' + (query ? 'No skills match your search.' : 'No skills yet. Create one below!') + '
'; + const empty = document.createElement('div'); + empty.style.cssText = 'padding:20px; text-align:center; color:#666'; + empty.textContent = query ? 'No skills match your search.' : 'No skills yet. Create one below!'; + list.appendChild(empty); return; } // Group by category @@ -2006,18 +2158,41 @@ const tags = (s.tags || []).join(', '); const div = document.createElement('div'); div.className = 'module-item'; - div.innerHTML = ` -
- ${name} -
${s.description || ''}
-
${tags ? '🏷 ' + tags : ''}
-
-
- - - -
- `; + const info = document.createElement('div'); + const strong = document.createElement('strong'); + strong.style.color = 'var(--success-color)'; + strong.textContent = name; + info.appendChild(strong); + const desc = document.createElement('div'); + desc.style.cssText = 'font-size:0.85em; color:#aaa'; + desc.textContent = s.description || ''; + info.appendChild(desc); + const tagsDiv = document.createElement('div'); + tagsDiv.style.cssText = 'font-size:0.75em; color:#666'; + tagsDiv.textContent = tags ? '🏷 ' + tags : ''; + info.appendChild(tagsDiv); + const actions = document.createElement('div'); + actions.style.cssText = 'display:flex;gap:4px'; + const viewBtn = document.createElement('button'); + viewBtn.className = 'btn'; + viewBtn.style.cssText = 'padding:4px 8px; font-size:0.8em'; + viewBtn.textContent = '👁'; + viewBtn.onclick = () => this.viewSkillUI(name); + const editBtn = document.createElement('button'); + editBtn.className = 'btn'; + editBtn.style.cssText = 'padding:4px 8px; font-size:0.8em'; + editBtn.textContent = '✏️'; + editBtn.onclick = () => this.editSkillUI(name); + const delBtn = document.createElement('button'); + delBtn.className = 'btn btn-danger'; + delBtn.style.cssText = 'padding:4px 8px; font-size:0.8em'; + delBtn.textContent = '✕'; + delBtn.onclick = () => this.deleteSkillUI(name); + actions.appendChild(viewBtn); + actions.appendChild(editBtn); + actions.appendChild(delBtn); + div.appendChild(info); + div.appendChild(actions); body.appendChild(div); } section.appendChild(body); @@ -2081,9 +2256,9 @@ alert(`Skill '${name}' already exists. Delete it first or use skill-patch command.`); return; } - let content = contentInput || `---\nname: ${name}\ndescription: ${description}\ntags: [${tags.join(', ')}]\ncategory: ${category}\n---\n## Steps\n1. (to be filled)\n`; + let content = contentInput || `---\nname: ${this._escapeYamlString(name)}\ndescription: ${this._escapeYamlString(description)}\ntags: [${tags.map(t => this._escapeYamlString(t)).join(', ')}]\ncategory: ${this._escapeYamlString(category)}\n---\n## Steps\n1. (to be filled)\n`; if (!content.startsWith('---')) { - content = `---\nname: ${name}\ndescription: ${description}\ntags: [${tags.join(', ')}]\ncategory: ${category}\n---\n${content}`; + content = `---\nname: ${this._escapeYamlString(name)}\ndescription: ${this._escapeYamlString(description)}\ntags: [${tags.map(t => this._escapeYamlString(t)).join(', ')}]\ncategory: ${this._escapeYamlString(category)}\n---\n${content}`; } fs.writeFile(`/skills/${name}.md`, content); const index = fs.fsReadJSON('/skills/index.json') || {}; -- cgit v1.2.3