aboutsummaryrefslogtreecommitdiff
path: root/modules/ext/skill-pack-core.js
diff options
context:
space:
mode:
Diffstat (limited to 'modules/ext/skill-pack-core.js')
-rw-r--r--modules/ext/skill-pack-core.js255
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();
}