aboutsummaryrefslogtreecommitdiff
path: root/modules/ext
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-06-06 06:00:27 +0200
committerAgando <roduccomm@gmail.com>2026-06-06 06:00:27 +0200
commitccc7d9956559d2195e20345e7a794662b52cd403 (patch)
tree757354fbfec5c75b4577b2cb620d5ab1582e19b1 /modules/ext
parentb538577cd0a4292e4cd4ca1c9f9829fc7e133caa (diff)
auto: 2026-06-06_06:00 changes
Diffstat (limited to 'modules/ext')
-rw-r--r--modules/ext/busybox.js2
-rw-r--r--modules/ext/editor.js11
-rw-r--r--modules/ext/mindmap.js30
-rw-r--r--modules/ext/skill-pack-core.js255
-rw-r--r--modules/ext/webdav.js4
5 files changed, 288 insertions, 14 deletions
diff --git a/modules/ext/busybox.js b/modules/ext/busybox.js
index 34221f8..53eae06 100644
--- a/modules/ext/busybox.js
+++ b/modules/ext/busybox.js
@@ -334,7 +334,7 @@ class BusyBoxModule {
const ua = navigator.userAgent;
const space = 'newos4';
const platform = navigator.platform || 'unknown';
- const version = 'WebX11 v4.2';
+ const version = 'WebX11 ' + (window.NEWOS4_VERSION || 'v4.0');
this.terminal.print(`Hostname: ${space}`, 'info');
this.terminal.print(`Platform: ${version} on ${platform}`, 'info');
this.terminal.print(`User Agent: ${ua}`, 'info');
diff --git a/modules/ext/editor.js b/modules/ext/editor.js
index 8c3ac0f..3a5f269 100644
--- a/modules/ext/editor.js
+++ b/modules/ext/editor.js
@@ -44,6 +44,17 @@ class EditorModule {
const ok = this.terminal.fs.writeFile(this._vfsPath, code);
if (ok) {
this.terminal.print('✅ Saved to ' + this._vfsPath, 'success');
+ // Mirror to _mods/ for test_module compatibility
+ const modMatch = this._vfsPath.match(/(?:^|\/)(\w+)\.js$/);
+ if (modMatch) {
+ const modName = modMatch[1];
+ this.terminal.fs.mkdirRecursive('_mods');
+ this.terminal.fs.writeFile('_mods/' + modName + '.js', code);
+ if (!this.terminal.fs.readFile('_mods/' + modName + '.orig.js')) {
+ this.terminal.fs.writeFile('_mods/' + modName + '.orig.js', 'placeholder');
+ }
+ this.terminal.print('📋 Mirrored to _mods/' + modName + '.js for live testing', 'info');
+ }
// Notify tutorial module for quest 4 (edit + save)
if (this.terminal.tutorialModule) {
this.terminal.tutorialModule.onFileSaved(this._vfsPath);
diff --git a/modules/ext/mindmap.js b/modules/ext/mindmap.js
index d0cab54..1daa85b 100644
--- a/modules/ext/mindmap.js
+++ b/modules/ext/mindmap.js
@@ -247,13 +247,18 @@ class MindmapModule {
cyan: '#22d3ee', magenta: '#e879f9', white: '#e0e0e0'
};
- const layout = { nodes: [], edges: [], width: 0, height: 0, _y: pad };
+ const layout = { nodes: [], edges: [], width: 0, height: 0, _y: pad, minX: Infinity, maxX: 0, minY: Infinity, maxY: 0 };
const layoutTree = (node, x) => {
const myY = layout._y;
const myX = x;
const color = colorMap[node.color] || '#818cf8';
layout.nodes.push({ x: myX, y: myY, w: nw, h: nh, text: node.text, color, id: node.id });
+ if (myX < layout.minX) layout.minX = myX;
+ const rightEdge = x + nw;
+ if (rightEdge > layout.maxX) layout.maxX = rightEdge;
+ const bottomEdge = myY + nh;
+ if (bottomEdge > layout.maxY) layout.maxY = bottomEdge;
if (node.children && node.children.length > 0) {
const totalW = node.children.length * nw + (node.children.length - 1) * hGap;
@@ -267,28 +272,33 @@ class MindmapModule {
}
layout._y -= nh + vGap;
}
- const rightEdge = x + nw;
- if (rightEdge > layout.width) layout.width = rightEdge;
};
layout._y = pad;
layoutTree(this.root, pad);
- layout.height = layout._y + nh + pad;
- // Build SVG
- let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${layout.width + pad} ${layout.height}" style="max-width:100%;background:#0a0a14;font-family:monospace;border-radius:8px;">`;
+ // Normalize coordinates — shift so minX becomes pad
+ const xShift = (layout.minX === Infinity ? 0 : -layout.minX) + pad;
+ layout.height = layout.maxY + pad;
+ const svgWidth = (layout.maxX - layout.minX) + pad * 2;
+
+ // Build SVG — apply xShift to all coordinates
+ let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${svgWidth} ${layout.height}" style="max-width:100%;background:#0a0a14;font-family:monospace;border-radius:8px;">`;
// Edges first (behind nodes)
for (const e of layout.edges) {
- const mx = (e.x1 + e.x2) / 2;
- svg += `<path d="M${e.x1},${e.y1} C${mx},${e.y1 + 20} ${mx},${e.y2 - 20} ${e.x2},${e.y2}" fill="none" stroke="#2a2a4a" stroke-width="1.5"/>`;
+ const sx = e.x1 + xShift, sy = e.y1;
+ const ex = e.x2 + xShift, ey = e.y2;
+ const mx = (sx + ex) / 2;
+ svg += `<path d="M${sx},${sy} C${mx},${sy + 20} ${mx},${ey - 20} ${ex},${ey}" fill="none" stroke="#2a2a4a" stroke-width="1.5"/>`;
}
// Nodes on top
for (const n of layout.nodes) {
- svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="${n.h}" rx="6" fill="${n.color}18" stroke="${n.color}" stroke-width="1.5"/>`;
+ const nx = n.x + xShift;
+ svg += `<rect x="${nx}" y="${n.y}" width="${n.w}" height="${n.h}" rx="6" fill="${n.color}18" stroke="${n.color}" stroke-width="1.5"/>`;
const text = n.text.length > 18 ? n.text.substring(0, 16) + '..' : n.text;
- svg += `<text x="${n.x + n.w / 2}" y="${n.y + n.h / 2 + 4}" text-anchor="middle" fill="#e0e0e0" font-size="10" font-family="monospace">${text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}</text>`;
+ svg += `<text x="${nx + n.w / 2}" y="${n.y + n.h / 2 + 4}" text-anchor="middle" fill="#e0e0e0" font-size="10" font-family="monospace">${text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}</text>`;
}
svg += '</svg>';
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();
}
diff --git a/modules/ext/webdav.js b/modules/ext/webdav.js
index 01856ce..a025cb2 100644
--- a/modules/ext/webdav.js
+++ b/modules/ext/webdav.js
@@ -633,8 +633,8 @@ class WebDAVModule {
}
const serialized = JSON.stringify(exportData, null, 2);
// Size check: abort if >5MB
- if (serialized.length > 5 * 1024 * 1024) {
- self.terminal.print('Backup too large: ' + self._formatSize(serialized.length) + ' (>5MB limit). Try reducing VFS size first.', 'error');
+ if (serialized.length > 52428800) {
+ self.terminal.print('Backup too large: ' + self._formatSize(serialized.length) + ' (>50MB limit). Try reducing VFS size first.', 'error');
return;
}
// Gzip payload