diff options
Diffstat (limited to 'modules/ext/skill-pack-core.js')
| -rw-r--r-- | modules/ext/skill-pack-core.js | 255 |
1 files changed, 254 insertions, 1 deletions
diff --git a/modules/ext/skill-pack-core.js b/modules/ext/skill-pack-core.js index f731594..41844bd 100644 --- a/modules/ext/skill-pack-core.js +++ b/modules/ext/skill-pack-core.js @@ -21,6 +21,14 @@ class SkillPackCoreModule { getHelpInfo() { return { 'skill-pack-core': 'Seed and manage built-in core skills (navigation, memory, search, modules, backup, agents, webdav, security, etc.)', + 'skill-list': 'skill-list [category] — List all skills, optionally filtered by category', + 'skill-view': 'skill-view <name> — View a skill\'s full content', + 'skill-create': 'skill-create <name> <desc> "<steps>" [tags] — Create a new skill', + 'skill-patch': 'skill-patch <name> "<content>" — Replace a skill\'s full content', + 'skill-delete': 'skill-delete <name> — Delete a skill', + 'skill-export': 'skill-export <name> — Export a skill as JSON', + 'skill-import': 'skill-import <json> — Import a skill from JSON string', + 'skill-sync': 'skill-sync — Rebuild skills index from /skills/ directory', }; } @@ -427,7 +435,42 @@ class SkillPackCoreModule { 5. **When to use:** - User wants to lock down the terminal → security-level 2 - User wants to allow a blocked command → cmd-allow <cmd> - - Debugging strange behavior → verify security-level (user may have set it higher)`) + - Debugging strange behavior → verify security-level (user may have set it higher)`), + + sk('mindmap-user-guide', 'How to create, view, and manage brainstorming mindmaps using the mindmap terminal module', ['mindmap', 'brainstorming', 'organization', 'planning'], 'productivity', +`## Steps +1. **Create a new mindmap:** + - mindmap new "<topic>" -t tag1,tag2 — creates a root node + - Tags are optional (e.g. planning, research, dev) + - Root gets auto-assigned a color + +2. **Add child nodes:** + - mindmap add <parent-id> "<idea>" -t tag — add a child + - Get the parent-id from mindmap show + - IDs are 4-char random strings (e.g. a3f8, x7k2) + +3. **View the mindmap:** + - mindmap show — full tree with Unicode box-drawing + - mindmap show <id> — subtree starting at that node + +4. **Edit nodes:** + - mindmap rename <id> "<new text>" — change text + - mindmap color <id> <color> — set color (red/green/yellow/blue/cyan/magenta/white) + - mindmap tag <id> <tag> — add a tag + +5. **Persistence:** + - mindmap save <name> — save to localStorage + - mindmap load <name> — restore + - mindmap list — show saved mindmaps + - mindmap delete <name> — remove saved + +6. **Export:** + - mindmap export [id] — Markdown bullet list output + +## When to use +- User wants to brainstorm, organize ideas, plan features +- User says "map out", "diagram", "brainstorm", "organize thoughts" +- Visualizing project structure or task decomposition`) ]; } registerCommands() { @@ -436,7 +479,217 @@ class SkillPackCoreModule { this.terminal.print(`Skill Pack: Core — ${count} skills available`, 'info'); this.terminal.print('Skills will auto-seed on load', 'info'); }; + this.commands['skill-list'] = (args) => this._cmdSkillList(args); + this.commands['skill-view'] = (args) => this._cmdSkillView(args); + this.commands['skill-create'] = (args) => this._cmdSkillCreate(args); + this.commands['skill-patch'] = (args) => this._cmdSkillPatch(args); + this.commands['skill-delete'] = (args) => this._cmdSkillDelete(args); + this.commands['skill-export'] = (args) => this._cmdSkillExport(args); + this.commands['skill-import'] = (args) => this._cmdSkillImport(args); + this.commands['skill-sync'] = () => this._cmdSkillSync(); + } + // ── Skill Commands ── + + _cmdSkillList(args) { + const fs = this.terminal.fs; + const index = fs.fsReadJSON('/skills/index.json') || {}; + const entries = Object.values(index); + const filter = args[0] ? args[0].toLowerCase() : null; + const filtered = filter ? entries.filter(s => s.category === filter || s.category?.toLowerCase() === filter) : entries; + if (filtered.length === 0) { + this.terminal.print(filter ? `No skills in category: ${args[0]}` : 'No skills installed.', 'info'); + return; + } + this.terminal.print(`📚 Skills (${filtered.length} of ${entries.length} total):`, 'info'); + // Group by category + const groups = {}; + for (const s of filtered) { + const cat = s.category || 'uncategorized'; + if (!groups[cat]) groups[cat] = []; + groups[cat].push(s); + } + for (const [cat, skills] of Object.entries(groups)) { + this.terminal.print(` ${cat}:`, ''); + for (const s of skills) { + const tags = s.tags?.length ? ` (${s.tags.join(', ')})` : ''; + this.terminal.print(` 📄 ${s.name} — ${s.description}${tags}`, ''); + } + } + } + + _cmdSkillView(args) { + if (!args[0]) { this.terminal.print('Usage: skill-view <name>', 'warning'); return; } + const name = args[0].replace(/\.md$/, ''); + const fs = this.terminal.fs; + const content = fs.readFile(`/skills/${name}.md`); + if (content === null || content === undefined) { + this.terminal.print(`Skill not found: ${name}`, 'error'); + return; + } + this.terminal.print(`=== ${name} ===`, 'info'); + const lines = content.split('\n'); + for (const line of lines) { + this.terminal.print(line, ''); + } + this.terminal.print(`=== End ${name} ===`, 'info'); + } + + _cmdSkillCreate(args) { + if (args.length < 3) { + this.terminal.print('Usage: skill-create <name> <description> "<markdown content>" [tags]', 'warning'); + return; + } + const name = args[0].replace(/[^a-z0-9-]/g, '').toLowerCase(); + if (!name) { this.terminal.print('Name must be kebab-case (letters, numbers, hyphens only)', 'error'); return; } + const desc = args[1]; + const content = args.slice(2).join(' '); + const tags = []; + if (content.includes('"')) { + // Parse quoted tags at end + } + const fs = this.terminal.fs; + // Check for duplicate + const index = fs.fsReadJSON('/skills/index.json') || {}; + if (index[name]) { + this.terminal.print(`Skill "${name}" already exists. Use skill-patch to update.`, 'warning'); + return; + } + const now = new Date().toISOString(); + const skillContent = `---\nname: ${name}\ndescription: ${desc}\ntags: [${tags.join(', ')}]\ncategory: general\n---\n${content}`; + fs.writeFile(`/skills/${name}.md`, skillContent); + index[name] = { name, description: desc, tags, category: 'general', created: now, updated: now }; + fs.fsWriteJSON('/skills/index.json', index); + this.terminal.print(`✅ Skill "${name}" created (${(skillContent.length / 1024).toFixed(1)}KB)`, 'success'); + } + + _cmdSkillPatch(args) { + if (args.length < 2) { + this.terminal.print('Usage: skill-patch <name> "<full content>"', 'warning'); + return; + } + const name = args[0].replace(/\.md$/, ''); + const content = args.slice(1).join(' '); + const fs = this.terminal.fs; + const index = fs.fsReadJSON('/skills/index.json') || {}; + if (!index[name]) { + this.terminal.print(`Skill not found: ${name}`, 'error'); + return; + } + fs.writeFile(`/skills/${name}.md`, content); + index[name].updated = new Date().toISOString(); + // Re-parse YAML frontmatter to update index + const headerMatch = content.match(/^---\n([\s\S]*?)\n---\n/); + if (headerMatch) { + const header = headerMatch[1]; + const d = header.match(/description:\s*(.+)/); + const c = header.match(/category:\s*(.+)/); + const t = header.match(/tags:\s*\[(.*?)\]/); + if (d) index[name].description = d[1].trim(); + if (c) index[name].category = c[1].trim(); + if (t) index[name].tags = t[1].split(',').map(x => x.trim()).filter(Boolean); + } + fs.fsWriteJSON('/skills/index.json', index); + this.terminal.print(`✅ Skill "${name}" updated`, 'success'); + } + + _cmdSkillDelete(args) { + if (!args[0]) { this.terminal.print('Usage: skill-delete <name>', 'warning'); return; } + const name = args[0].replace(/\.md$/, ''); + const fs = this.terminal.fs; + const index = fs.fsReadJSON('/skills/index.json') || {}; + if (!index[name]) { + this.terminal.print(`Skill not found: ${name}`, 'error'); + return; + } + fs.deleteFile(`/skills/${name}.md`); + delete index[name]; + fs.fsWriteJSON('/skills/index.json', index); + this.terminal.print(`🗑️ Skill "${name}" deleted`, 'success'); + } + + _cmdSkillExport(args) { + if (!args[0]) { this.terminal.print('Usage: skill-export <name>', 'warning'); return; } + const name = args[0].replace(/\.md$/, ''); + const fs = this.terminal.fs; + const index = fs.fsReadJSON('/skills/index.json') || {}; + if (!index[name]) { + this.terminal.print(`Skill not found: ${name}`, 'error'); + return; + } + const content = fs.readFile(`/skills/${name}.md`); + const exportObj = { + ...index[name], + content: content || '' + }; + this.terminal.print(JSON.stringify(exportObj, null, 2), ''); } + + _cmdSkillImport(args) { + if (args.length === 0) { this.terminal.print('Usage: skill-import <json_string> or pipe a JSON object', 'warning'); return; } + const jsonStr = args.join(' '); + let data; + try { data = JSON.parse(jsonStr); } catch (e) { + this.terminal.print('Invalid JSON. Please provide a valid skill object.', 'error'); + return; + } + if (!data.name || !data.content) { + this.terminal.print('JSON must include "name" and "content" fields', 'error'); + return; + } + const fs = this.terminal.fs; + const index = fs.fsReadJSON('/skills/index.json') || {}; + const now = new Date().toISOString(); + const existing = index[data.name]; + fs.writeFile(`/skills/${data.name}.md`, data.content); + index[data.name] = { + name: data.name, + description: data.description || data.name, + tags: data.tags || [], + category: data.category || 'general', + created: existing?.created || now, + updated: now + }; + fs.fsWriteJSON('/skills/index.json', index); + this.terminal.print(`✅ Skill "${data.name}" imported (${existing ? 'updated' : 'created'})`, 'success'); + } + + _cmdSkillSync() { + const fs = this.terminal.fs; + const files = fs.ls('/skills') || []; + const mdFiles = files.filter(f => f.type === 'file' && f.name.endsWith('.md') && f.name !== 'index.json'); + let synced = 0; + const index = {}; + for (const f of mdFiles) { + const content = fs.readFile(`/skills/${f.name}`); + if (!content) continue; + const name = f.name.replace(/\.md$/, ''); + const headerMatch = content.match(/^---\n([\s\S]*?)\n---\n/); + let description = name; + let tags = []; + let category = 'general'; + if (headerMatch) { + const h = headerMatch[1]; + const d = h.match(/description:\s*(.+)/); + const c = h.match(/category:\s*(.+)/); + const t = h.match(/tags:\s*\[(.*?)\]/); + if (d) description = d[1].trim(); + if (c) category = c[1].trim(); + if (t) tags = t[1].split(',').map(x => x.trim()).filter(Boolean); + } + index[name] = { + name, + description, + tags, + category, + created: new Date().toISOString(), + updated: new Date().toISOString() + }; + synced++; + } + fs.fsWriteJSON('/skills/index.json', index); + this.terminal.print(`📚 Skill index rebuilt: ${synced} skills synced`, 'success'); + } + init() { this.seed(); } |