aboutsummaryrefslogtreecommitdiff
path: root/modules/core/terminal.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-06-23 06:00:45 +0200
committerAgando <roduccomm@gmail.com>2026-06-23 06:00:45 +0200
commite9b75cd3a801689f57bc8a013f14c058e57e3b46 (patch)
treea40a2b9ab142e4f3432d4686f52d177535b51d74 /modules/core/terminal.js
parent6f6161562c5f1028256f3163083b665719741646 (diff)
auto: 2026-06-23_06:00 changes
Diffstat (limited to 'modules/core/terminal.js')
-rw-r--r--modules/core/terminal.js168
1 files changed, 144 insertions, 24 deletions
diff --git a/modules/core/terminal.js b/modules/core/terminal.js
index 2178e85..b5f50a6 100644
--- a/modules/core/terminal.js
+++ b/modules/core/terminal.js
@@ -1903,32 +1903,69 @@
refreshSkillsList() {
const list = document.getElementById('skills-list');
list.innerHTML = '';
+ const statsEl = document.getElementById('skills-stats');
const fs = this.fs;
fs.mkdirRecursive('/skills');
const index = fs.fsReadJSON('/skills/index.json') || {};
const names = Object.keys(index);
- if (names.length === 0) {
- list.innerHTML = '<div style="padding:20px; text-align:center; color:#666">No skills yet. Create one below!</div>';
+ // Search filter
+ const query = (document.getElementById('skills-search-input')?.value || '').toLowerCase().trim();
+ let filtered = names;
+ if (query) {
+ filtered = names.filter(n => {
+ const s = index[n];
+ return n.includes(query) || (s.description || '').toLowerCase().includes(query) || (s.tags || []).some(t => t.toLowerCase().includes(query)) || (s.category || '').toLowerCase().includes(query);
+ });
+ }
+ // Stats
+ if (statsEl) {
+ const cats = new Set(Object.values(index).map(s => s.category || 'general'));
+ statsEl.innerHTML = `<span>๐Ÿ“š ${names.length} skills</span><span>๐Ÿ“‚ ${cats.size} categories</span>${query ? `<span>๐Ÿ” ${filtered.length} match '${query}'</span>` : ''}`;
+ }
+ if (filtered.length === 0) {
+ list.innerHTML = '<div style="padding:20px; text-align:center; color:#666">' + (query ? 'No skills match your search.' : 'No skills yet. Create one below!') + '</div>';
return;
}
- names.forEach(name => {
+ // Group by category
+ const groups = {};
+ for (const name of filtered) {
const s = index[name];
- const tags = (s.tags || []).join(', ');
- const div = document.createElement('div');
- div.className = 'module-item';
- div.innerHTML = `
- <div>
- <strong style="color:var(--success-color)">${name}</strong>
- <div style="font-size:0.85em; color:#aaa">${s.description || ''}</div>
- <div style="font-size:0.75em; color:#666">${s.category || 'general'}${tags ? ' ยท ' + tags : ''}</div>
- </div>
- <div style="display:flex;gap:4px">
- <button class="btn" onclick="webx11.viewSkillUI('${name}')" style="padding:4px 8px; font-size:0.8em">๐Ÿ‘</button>
- <button class="btn btn-danger" onclick="webx11.deleteSkillUI('${name}')" style="padding:4px 8px; font-size:0.8em">โœ•</button>
- </div>
- `;
- list.appendChild(div);
- });
+ const cat = s.category || 'general';
+ if (!groups[cat]) groups[cat] = [];
+ groups[cat].push(name);
+ }
+ const catOrder = Object.keys(groups).sort();
+ for (const cat of catOrder) {
+ const section = document.createElement('div');
+ section.style.marginBottom = '6px';
+ const header = document.createElement('div');
+ header.style.cssText = 'font-size:0.8em; font-weight:600; color:var(--info-color); padding:4px 0; cursor:pointer; display:flex; align-items:center; gap:4px;';
+ header.textContent = '๐Ÿ“ ' + cat + ' (' + groups[cat].length + ')';
+ header.onclick = () => { const body = header.nextElementSibling; if (body) body.style.display = body.style.display === 'none' ? '' : 'none'; };
+ section.appendChild(header);
+ const body = document.createElement('div');
+ for (const name of groups[cat]) {
+ const s = index[name];
+ const tags = (s.tags || []).join(', ');
+ const div = document.createElement('div');
+ div.className = 'module-item';
+ div.innerHTML = `
+ <div>
+ <strong style="color:var(--success-color)">${name}</strong>
+ <div style="font-size:0.85em; color:#aaa">${s.description || ''}</div>
+ <div style="font-size:0.75em; color:#666">${tags ? '๐Ÿท ' + tags : ''}</div>
+ </div>
+ <div style="display:flex;gap:4px">
+ <button class="btn" onclick="webx11.viewSkillUI('${name}')" style="padding:4px 8px; font-size:0.8em">๐Ÿ‘</button>
+ <button class="btn" onclick="webx11.editSkillUI('${name}')" style="padding:4px 8px; font-size:0.8em">โœ๏ธ</button>
+ <button class="btn btn-danger" onclick="webx11.deleteSkillUI('${name}')" style="padding:4px 8px; font-size:0.8em">โœ•</button>
+ </div>
+ `;
+ body.appendChild(div);
+ }
+ section.appendChild(body);
+ list.appendChild(section);
+ }
}
viewSkillUI(name) {
const fs = this.fs;
@@ -1937,15 +1974,33 @@
document.getElementById('skill-preview-title').textContent = name;
document.getElementById('skill-preview-content').textContent = content;
document.getElementById('skill-preview').style.display = 'flex';
+ document.getElementById('skill-preview-expand-btn').textContent = 'โ›ถ Expand';
+ document.getElementById('skill-preview').style.maxHeight = '250px';
}
closeSkillPreview() {
document.getElementById('skill-preview').style.display = 'none';
}
+ toggleSkillPreviewSize() {
+ const preview = document.getElementById('skill-preview');
+ const btn = document.getElementById('skill-preview-expand-btn');
+ if (preview.style.maxHeight === 'none' || preview.style.maxHeight === '80vh') {
+ preview.style.maxHeight = '250px';
+ btn.textContent = 'โ›ถ Expand';
+ } else {
+ preview.style.maxHeight = '80vh';
+ btn.textContent = 'โ›ถ Collapse';
+ }
+ }
+ editSkillUI(name) {
+ this.closeSkillsManager();
+ if (typeof webx11.dispatchCommand === 'function') {
+ setTimeout(() => webx11.dispatchCommand('edit', ['/skills/' + name + '.md']), 200);
+ }
+ }
deleteSkillUI(name) {
if (!confirm(`Delete skill "${name}"?`)) return;
const fs = this.fs;
fs.mkdirRecursive('/skills');
- // Actually remove from VFS tree
if (fs.root && fs.root.content && fs.root.content['skills'] && fs.root.content['skills'].content) {
delete fs.root.content['skills'].content[`${name}.md`];
}
@@ -1959,6 +2014,7 @@
createSkillFromUI() {
const name = document.getElementById('skill-name-input').value.trim().replace(/\s+/g, '-').toLowerCase();
const description = document.getElementById('skill-desc-input').value.trim() || 'No description';
+ const category = document.getElementById('skill-category-input')?.value || 'general';
const tags = document.getElementById('skill-tags-input').value.trim().split(',').map(t => t.trim()).filter(Boolean);
const contentInput = document.getElementById('skill-content-input').value.trim();
if (!name) { alert('Skill name is required'); return; }
@@ -1968,13 +2024,13 @@
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: general\n---\n## Steps\n1. (to be filled)\n`;
+ let content = contentInput || `---\nname: ${name}\ndescription: ${description}\ntags: [${tags.join(', ')}]\ncategory: ${category}\n---\n## Steps\n1. (to be filled)\n`;
if (!content.startsWith('---')) {
- content = `---\nname: ${name}\ndescription: ${description}\ntags: [${tags.join(', ')}]\ncategory: general\n---\n${content}`;
+ content = `---\nname: ${name}\ndescription: ${description}\ntags: [${tags.join(', ')}]\ncategory: ${category}\n---\n${content}`;
}
fs.writeFile(`/skills/${name}.md`, content);
const index = fs.fsReadJSON('/skills/index.json') || {};
- index[name] = { name, description, category: 'general', tags };
+ index[name] = { name, description, category, tags };
fs.fsWriteJSON('/skills/index.json', index);
this.print(`Skill '${name}' created`, 'success');
document.getElementById('skill-name-input').value = '';
@@ -1983,6 +2039,70 @@
document.getElementById('skill-content-input').value = '';
this.refreshSkillsList();
}
+ exportAllSkillsUI() {
+ const fs = this.fs;
+ const index = fs.fsReadJSON('/skills/index.json') || {};
+ const names = Object.keys(index);
+ if (names.length === 0) { alert('No skills to export.'); return; }
+ const exportData = {};
+ for (const name of names) {
+ const content = fs.readFile(`/skills/${name}.md`);
+ if (content !== null) {
+ exportData[name] = { ...index[name], content };
+ }
+ }
+ const json = JSON.stringify(exportData, null, 2);
+ const blob = new Blob([json], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'skills-export-' + new Date().toISOString().slice(0, 10) + '.json';
+ a.click();
+ URL.revokeObjectURL(url);
+ this.print(`๐Ÿ“ค Exported ${names.length} skills`, 'success');
+ }
+ importSkillsUI(event) {
+ const file = event.target.files[0];
+ if (!file) return;
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ try {
+ const data = JSON.parse(e.target.result);
+ const fs = this.fs;
+ fs.mkdirRecursive('/skills');
+ const index = fs.fsReadJSON('/skills/index.json') || {};
+ let imported = 0;
+ for (const [name, skill] of Object.entries(data)) {
+ if (!skill.content) continue;
+ fs.writeFile(`/skills/${name}.md`, skill.content);
+ index[name] = {
+ name,
+ description: skill.description || name,
+ tags: skill.tags || [],
+ category: skill.category || 'general',
+ created: (index[name] && index[name].created) || new Date().toISOString(),
+ updated: new Date().toISOString()
+ };
+ imported++;
+ }
+ fs.fsWriteJSON('/skills/index.json', index);
+ this.print(`๐Ÿ“ฅ Imported ${imported} skills`, 'success');
+ this.refreshSkillsList();
+ } catch (err) {
+ alert('Invalid JSON file: ' + err.message);
+ }
+ };
+ reader.readAsText(file);
+ event.target.value = '';
+ }
+ syncSkillsToWebdavUI() {
+ const mod = this.moduleManager && this.moduleManager.modules['SkillPackCoreModule'];
+ if (mod && mod.commands && mod.commands['skill-webdav-sync']) {
+ mod.commands['skill-webdav-sync']();
+ } else {
+ this.print('WebDAV sync not available. Is skill-pack-core loaded?', 'warning');
+ }
+ }
updateOllamaStatus() {
const statusDiv = document.getElementById('ollama-status');
const statusText = document.getElementById('ollama-status-text');
@@ -2249,7 +2369,7 @@
'module-my.js','module-searxng.js','module-servermon.js','module-ollamamodels.js',
'module-config.js','module-simplegui.js','module-io.js','module-dynamicprompts.js',
'module-aiassist.js','module-aiasm.js','module-smartctx.js','module-duckduckgo.js',
- 'module-fetcher.js','module-skills.js','module-chatstudio.js'
+ 'module-fetcher.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','media.js'