aboutsummaryrefslogtreecommitdiff
path: root/modules/core/module-skills.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-05-25 18:02:02 +0200
committerAgando <roduccomm@gmail.com>2026-05-25 18:02:02 +0200
commit4baa484b84c5184286cac2743575251454a5ee2b (patch)
treecb9002f93644f363d0f35831dc97e6492bbb0ed6 /modules/core/module-skills.js
auto: 2026-05-25_18:02 changes
Diffstat (limited to 'modules/core/module-skills.js')
-rw-r--r--modules/core/module-skills.js271
1 files changed, 271 insertions, 0 deletions
diff --git a/modules/core/module-skills.js b/modules/core/module-skills.js
new file mode 100644
index 0000000..ed6e14f
--- /dev/null
+++ b/modules/core/module-skills.js
@@ -0,0 +1,271 @@
+// Extracted from newOS4 index.html — SkillsModule (v4.2 — Self-improving AI)
+ class SkillsModule {
+ constructor() { this.commands = {}; this.terminal = null; }
+ getHelpInfo() {
+ return {
+ 'skill-list': 'skill-list — List all installed skills',
+ 'skill-view': 'skill-view <name> — View the content of a skill',
+ 'skill-create': 'skill-create <name> [desc] [category] [tags] — Create a new skill',
+ 'skill-patch': 'skill-patch <name> [content...] — Update skill content (no args to view current)',
+ 'skill-delete': 'skill-delete <name> — Delete a skill',
+ 'skill-export': 'skill-export <name> — Export a skill to WebDAV',
+ 'skill-import': 'skill-import <name> — Import a skill from WebDAV',
+ 'skill-sync': 'skill-sync — Bidirectional sync all skills with WebDAV',
+ };
+ }
+ ensureSkillsDir() {
+ if (this.terminal && this.terminal.fs) {
+ const fs = this.terminal.fs;
+ // Ensure /skills/ directory exists
+ if (!fs.root.content['skills']) {
+ fs.root.content['skills'] = { type: 'dir', content: {} };
+ fs.saveToStorage();
+ }
+ }
+ }
+ registerCommands() {
+ this.commands['skill-list'] = () => this.listSkills();
+ this.commands['skill-view'] = (args) => this.viewSkill(args);
+ this.commands['skill-create'] = (args) => this.createSkill(args);
+ this.commands['skill-patch'] = (args) => this.patchSkill(args);
+ this.commands['skill-delete'] = (args) => this.deleteSkill(args);
+ this.commands['skill-export'] = (args) => this.exportSkill(args);
+ this.commands['skill-import'] = (args) => this.importSkill(args);
+ this.commands['skill-sync'] = () => this.syncSkills();
+ }
+ listSkills() {
+ this.ensureSkillsDir();
+ const index = this.terminal.fs.fsReadJSON('/skills/index.json') || {};
+ const names = Object.keys(index);
+ if (names.length === 0) {
+ this.terminal.print('No skills found.', 'info');
+ return;
+ }
+ this.terminal.print(`=== Skills (${names.length}) ===`, 'info');
+ names.forEach(name => {
+ const s = index[name];
+ const tags = (s.tags || []).join(', ');
+ this.terminal.print(`📘 ${name} — ${s.description} [${s.category}] ${tags ? '(' + tags + ')' : ''}`);
+ });
+ }
+ viewSkill(args) {
+ const name = (args[0] || '').replace(/\.md$/, '');
+ if (!name) {
+ this.terminal.print('Usage: skill-view <name>', 'warning');
+ return;
+ }
+ const content = this.terminal.fs.fsRead(`/skills/${name}.md`);
+ if (!content) {
+ this.terminal.print(`Skill '${name}' not found`, 'error');
+ return;
+ }
+ this.terminal.print(`=== ${name} ===`, 'info');
+ this.terminal.print(content, 'info');
+ }
+ createSkill(args) {
+ this.ensureSkillsDir();
+ const name = args[0];
+ if (!name) {
+ this.terminal.print('Usage: skill-create <name> [description] [category] [tag1,tag2,...]', 'warning');
+ return;
+ }
+ const description = args[1] || 'No description';
+ const category = args[2] || 'general';
+ const tags = (args[3] || '').split(',').filter(Boolean);
+ const fs = this.terminal.fs;
+ if (fs.fsRead(`/skills/${name}.md`)) {
+ this.terminal.print(`Skill '${name}' already exists. Use skill-patch to modify it.`, 'warning');
+ return;
+ }
+ const content = '---\nname: ' + name + '\ndescription: ' + description + '\ntags: [' + tags.join(', ') + ']\ncategory: ' + category + '\n---\n## Steps\n1. (to be filled)\n';
+ fs.fsWrite(`/skills/${name}.md`, content);
+ const index = fs.fsReadJSON('/skills/index.json') || {};
+ index[name] = { name, description, category, tags };
+ fs.fsWriteJSON('/skills/index.json', index);
+ this.terminal.print(`Skill '${name}' created. Edit its content with skill-patch ${name}`, 'success');
+ }
+ patchSkill(args) {
+ const name = args[0];
+ if (!name) {
+ this.terminal.print('Usage: skill-patch <name> [content...]', 'warning');
+ return;
+ }
+ const fs = this.terminal.fs;
+ const existing = fs.fsRead(`/skills/${name}.md`);
+ if (!existing) {
+ this.terminal.print(`Skill '${name}' not found. Use skill-create first.`, 'error');
+ return;
+ }
+ if (args.length < 2) {
+ // No new content — show the skill so user can copy-paste
+ this.terminal.print(`=== ${name} (current content) ===`, 'info');
+ this.terminal.print(existing, 'info');
+ this.terminal.print('---', 'info');
+ this.terminal.print('Pass new content as additional arguments: skill-patch <name> <full content>', 'warning');
+ return;
+ }
+ const newContent = args.slice(1).join(' ');
+ fs.fsWrite(`/skills/${name}.md`, newContent);
+ this.terminal.print(`Skill '${name}' patched`, 'success');
+ }
+ deleteSkill(args) {
+ const name = (args[0] || '').replace(/\.md$/, '');
+ if (!name) {
+ this.terminal.print('Usage: skill-delete <name>', 'warning');
+ return;
+ }
+ const fs = this.terminal.fs;
+ if (!fs.fsRead(`/skills/${name}.md`)) {
+ this.terminal.print(`Skill '${name}' not found`, 'error');
+ return;
+ }
+ fs.fsWrite(`/skills/${name}.md`, '');
+ const index = fs.fsReadJSON('/skills/index.json') || {};
+ delete index[name];
+ fs.fsWriteJSON('/skills/index.json', index);
+ this.terminal.print(`Skill '${name}' deleted`, 'success');
+ }
+ getWebDAVModule() {
+ return this.terminal && this.terminal.moduleManager.modules['WebDAVModule'];
+ }
+ async exportSkill(args) {
+ const name = (args[0] || '').replace(/\.md$/, '');
+ if (!name) {
+ this.terminal.print('Usage: skill-export <name>', 'warning');
+ return;
+ }
+ const wd = this.getWebDAVModule();
+ if (!wd || !wd.config || !wd.config.url) {
+ this.terminal.print('WebDAV not configured. Use webdav-config first.', 'warning');
+ return;
+ }
+ this.ensureSkillsDir();
+ const fs = this.terminal.fs;
+ const content = fs.fsRead(`/skills/${name}.md`);
+ if (!content) {
+ this.terminal.print(`Skill '${name}' not found in VFS`, 'error');
+ return;
+ }
+ const index = fs.fsReadJSON('/skills/index.json') || {};
+ const meta = index[name];
+ try {
+ // Ensure remote skills dir
+ if (typeof wd._ensureDir === 'function') await wd._ensureDir('skills');
+ // Push the skill file
+ const resp1 = await wd._webdavRequest('PUT', 'skills/' + name + '.md', {
+ body: content,
+ headers: { 'Content-Type': 'text/markdown' }
+ });
+ if (!resp1.ok && resp1.status !== 201 && resp1.status !== 204) {
+ throw new Error('Upload failed: HTTP ' + resp1.status);
+ }
+ // Push updated index
+ const indexResp = await wd._webdavRequest('PUT', 'skills/index.json', {
+ body: JSON.stringify(index, null, 2),
+ headers: { 'Content-Type': 'application/json' }
+ });
+ if (indexResp.ok || indexResp.status === 201 || indexResp.status === 204) {
+ this.terminal.print(`Skill '${name}' exported to WebDAV skills/`, 'success');
+ }
+ } catch (e) {
+ this.terminal.print('Export failed: ' + e.message, 'error');
+ }
+ }
+ async importSkill(args) {
+ const name = (args[0] || '').replace(/\.md$/, '');
+ if (!name) {
+ this.terminal.print('Usage: skill-import <name>', 'warning');
+ return;
+ }
+ const wd = this.getWebDAVModule();
+ if (!wd || !wd.config || !wd.config.url) {
+ this.terminal.print('WebDAV not configured. Use webdav-config first.', 'warning');
+ return;
+ }
+ try {
+ const resp = await wd._webdavRequest('GET', 'skills/' + name + '.md');
+ if (!resp.ok) throw new Error('HTTP ' + resp.status);
+ const content = await resp.text();
+ this.ensureSkillsDir();
+ const fs = this.terminal.fs;
+ fs.fsWrite(`/skills/${name}.md`, content);
+ // Try to pull the index to get metadata; if fails, create minimal entry
+ let meta = { name, description: 'Imported from WebDAV', category: 'general', tags: [] };
+ try {
+ const indexResp = await wd._webdavRequest('GET', 'skills/index.json');
+ if (indexResp.ok) {
+ const remoteIndex = JSON.parse(await indexResp.text());
+ if (remoteIndex && remoteIndex[name]) {
+ meta = remoteIndex[name];
+ }
+ }
+ } catch (e) { /* use default meta */ }
+ const index = fs.fsReadJSON('/skills/index.json') || {};
+ index[name] = meta;
+ fs.fsWriteJSON('/skills/index.json', index);
+ this.terminal.print(`Skill '${name}' imported from WebDAV (${content.length} bytes)`, 'success');
+ } catch (e) {
+ this.terminal.print('Import failed: ' + e.message, 'error');
+ }
+ }
+ async syncSkills() {
+ const wd = this.getWebDAVModule();
+ if (!wd || !wd.config || !wd.config.url) {
+ this.terminal.print('WebDAV not configured. Use webdav-config first.', 'warning');
+ return;
+ }
+ this.ensureSkillsDir();
+ const fs = this.terminal.fs;
+ const localIndex = fs.fsReadJSON('/skills/index.json') || {};
+ const localNames = new Set(Object.keys(localIndex));
+ this.terminal.print('Syncing skills with WebDAV...', 'info');
+ let imported = 0, exported = 0;
+ try {
+ // Try to get remote index
+ let remoteIndex = {};
+ try {
+ const resp = await wd._webdavRequest('GET', 'skills/index.json');
+ if (resp.ok) {
+ remoteIndex = JSON.parse(await resp.text()) || {};
+ }
+ } catch (e) { /* no remote index yet */ }
+ const remoteNames = new Set(Object.keys(remoteIndex));
+ // Export local-only skills to remote
+ for (const name of localNames) {
+ if (!remoteNames.has(name)) {
+ const content = fs.fsRead(`/skills/${name}.md`);
+ if (content) {
+ await wd._ensureDir('skills');
+ await wd._webdavRequest('PUT', 'skills/' + name + '.md', {
+ body: content,
+ headers: { 'Content-Type': 'text/markdown' }
+ });
+ exported++;
+ }
+ }
+ }
+ // Import remote-only skills to local
+ for (const name of remoteNames) {
+ if (!localNames.has(name)) {
+ const resp = await wd._webdavRequest('GET', 'skills/' + name + '.md');
+ if (resp.ok) {
+ const content = await resp.text();
+ fs.fsWrite(`/skills/${name}.md`, content);
+ localIndex[name] = remoteIndex[name];
+ imported++;
+ }
+ }
+ }
+ // Push updated local index back to remote
+ fs.fsWriteJSON('/skills/index.json', localIndex);
+ await wd._ensureDir('skills');
+ await wd._webdavRequest('PUT', 'skills/index.json', {
+ body: JSON.stringify(localIndex, null, 2),
+ headers: { 'Content-Type': 'application/json' }
+ });
+ this.terminal.print(`Sync complete: ${exported} exported, ${imported} imported`, 'success');
+ } catch (e) {
+ this.terminal.print('Sync failed: ' + e.message, 'error');
+ }
+ }
+ }