aboutsummaryrefslogtreecommitdiff
path: root/.bkp-fix-20261023_143429/skill-pack-core.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-11-09 15:36:23 +0100
committerAgando <roduccomm@gmail.com>2026-11-09 15:36:23 +0100
commita065a4c30be748b0c8fc6dd0b5ee1dc4d3de4b1c (patch)
tree8a84adabfccee9994937cd71888170ab205b9361 /.bkp-fix-20261023_143429/skill-pack-core.js
parenta46ebee30cfbbac86abb45068411bc28e1c65503 (diff)
chore(newos4): commit accumulated maintenance outputHEADv4.6.156main
The daily collector and the feedback pipeline had left 57 modified files uncommitted (skills, modules-index, CHANGELOG, feedback decisions, debug counters). The public mirror publishes git HEAD, so without this the published repo stayed behind the tree - and it blocked the publisher outright once uncommitted subtrees started failing loudly.
Diffstat (limited to '.bkp-fix-20261023_143429/skill-pack-core.js')
-rw-r--r--.bkp-fix-20261023_143429/skill-pack-core.js1150
1 files changed, 1150 insertions, 0 deletions
diff --git a/.bkp-fix-20261023_143429/skill-pack-core.js b/.bkp-fix-20261023_143429/skill-pack-core.js
new file mode 100644
index 0000000..f204e70
--- /dev/null
+++ b/.bkp-fix-20261023_143429/skill-pack-core.js
@@ -0,0 +1,1150 @@
+class SkillPackCoreModule {
+ constructor() {
+ this.commands = {};
+ this.commandsMeta = {
+ 'skill-list': { argType: 'enum', enumFn: '_getSkillCategories' }, 'skill-view': { argType: 'file' },
+ 'skill-create': { argType: 'dir' }, 'skill-patch': { argType: 'file' },
+ 'skill-delete': { argType: 'file' }, 'skill-export': { argType: 'file' },
+ 'skill-sync': { argType: 'file' },
+ };
+ this.terminal = null;
+ this._seeded = false;
+ this._skills = this._buildSkills();
+ }
+
+ requirements() {
+ try {
+ const fs = this.terminal && this.terminal.fs;
+ if (fs && fs.readFile('/skills/_seeded')) {
+ return { met: true, missing: [] };
+ }
+ return { met: false, missing: ['Skills directory not initialized. Will be created on first use.'] };
+ } catch (e) {
+ return { met: true, missing: [] };
+ }
+ }
+
+ 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',
+ 'learn-skill': 'learn-skill <topic> — Reflect on topic, draft a skill from experience',
+ 'skill-validate': 'skill-validate <name> — Validate skill structure and get confidence score',
+ 'skill-audit': 'skill-audit — Audit skills for stale, duplicate, and broken references',
+ 'skill-restore': 'skill-restore <name> — Restore an archived skill',
+ 'skill-webdav-sync': 'skill-webdav-sync — Push all skills to WebDAV as JSON files',
+ };
+ }
+
+ _buildSkills() {
+ const s = (name, desc, tags, cat, steps) => ({
+ name, description: desc, tags, category: cat,
+ content: '---\nname: ' + name + '\ndescription: ' + desc + '\ntags: [' + tags.join(', ') + ']\ncategory: ' + cat + '\n---\n## Steps\n' + steps
+ });
+
+ const sk = (name, desc, tags, cat, content) => ({
+ name, description: desc, tags, category: cat,
+ content: '---\nname: ' + name + '\ndescription: ' + desc + '\ntags: [' + tags.join(', ') + ']\ncategory: ' + cat + '\n---\n' + content
+ });
+
+ return [
+ s('navigating-the-terminal', 'How to explore the WebX11 terminal, find commands, check status, and navigate the filesystem', ['terminal', 'navigation', 'help', 'filesystem'], 'system',
+`1. **Get an overview of available commands:**
+ - Type "help" for a full command list sorted by category
+ - Type "status" to see filesystem state, loaded modules, Ollama status
+ - For a specific command, use help <command>
+ - Type "commands" for a compact list with module source info
+ - Type "check" for system health including module requirements
+
+2. **Explore the filesystem:**
+ - Use ls, ll, tree to browse directories
+ - Use cd <dir> to navigate, pwd to show current path
+ - Use cat or edit to view file contents
+ - Use find or grep to search files
+ - Use du to see disk usage
+
+3. **Understand system state:**
+ - mem — memory usage (auto-shown in header on boot)
+ - status — network, files, modules, Ollama
+ - modules — module manager GUI
+ - health-check — VFS size, modules, logs, trash, skills, tool failures
+ - checkpoint-list — VFS checkpoint snapshots
+
+4. **Agent / Orchestration state:**
+ - agent-list — registered agents (type, capabilities, status)
+ - orchestrate — list active orchestrated agents
+ - lease-list — show active leases with TTL
+
+5. **When user asks a vague question:**
+ - First run status and help to understand the current state
+ - Then ask clarifying questions based on what you found`),
+
+ s('memory-and-profile', 'How to use the user profile system, session memory, and pattern tracking for persistent context', ['memory', 'profile', 'session', 'persistence'], 'system',
+`1. **User Profile (persists across sessions):**
+ - user_get(key) — read any profile field (e.g. "preferredModel", "fontSize")
+ - user_set(key, value) — write to profile, auto-persists to localStorage
+ - user_list — show all stored profile keys
+ - user_forget(key) — delete a profile key
+ - Dot notation supported: user_set knownFacts.name "Alex"
+
+2. **Session Memory (VFS-based daily logs):**
+ - session_save(summary) — save a summary of what was accomplished
+ - session_load(date) — read a specific day's log (format: YYYY-MM-DD)
+ - session_recent(count) — show last N session summaries
+ - session_stats — aggregate totals across all sessions
+ - Recent sessions auto-load into context on first message
+ - systemd-style journal: use logs and logrotate commands for persistent logging
+
+3. **Conversation Patterns:**
+ - Pattern tracking runs automatically — frequent topics surface as hints
+ - my-set, my-get, my-list store additional personal data
+ - AI conversation context persists within a session (use ai-clear to reset)
+ - debug command toggles real-time debug panel with Copy/Export/VFS buttons
+
+4. **Checkpoints (VFS snapshots):**
+ - checkpoint [name] — snapshot current VFS state
+ - checkpoint-list — list all checkpoints
+ - rollback <name> — restore from checkpoint
+ - checkpoint-del <name> — delete a checkpoint
+
+5. **When to use:**
+ - At start: call session_recent(3) to see what was worked on before
+ - After identifying user preferences: call user_set to store them
+ - After complex tasks: call session_save with a brief summary
+ - Before risky edits: call checkpoint to create a restore point`),
+
+ s('web-search-and-fetch', 'How to search the web and fetch URL content using the WebX11 proxy API', ['search', 'fetch', 'web', 'proxy', 'api'], 'research',
+`1. **Search the web:**
+ - Use search <query> or web_search(query) — uses /api/search which queries SearXNG + DDG fallback
+ - Results include: title, url, content snippet, source engine
+ - Always available (same-origin, no CORS issues)
+ - 5-minute server-side cache for repeated queries
+ - Alternative: use ddg or duck <query> for DuckDuckGo direct, or s <query> as alias
+
+2. **Fetch URL content:**
+ - Use fetch <url> or web_fetch(url) — uses /api/extract which strips HTML, extracts clean text
+ - Returns: title, content (up to 4000 chars), word_count, extraction method
+ - SSRF protection blocks internal network addresses
+ - 15-minute server-side cache
+
+3. **Search flags (via terminal search command):**
+ - search <query> --summarize — adds AI-generated overview via Ollama
+ - search <query> --fetch — fetches content snippets for top 3 results
+
+4. **When to use:**
+ - User asks about current events, facts, research → use search <query>
+ - User says "read this article" or provides a URL → use fetch <url>
+ - For in-depth analysis: search first, then fetch top results`),
+
+ s('module-management', 'How to view, edit, test, reload modules and manage startup configuration', ['modules', 'editing', 'code', 'self-improvement'], 'dev',
+`1. **View module source:**
+ - view_module(name) — fetches original source or reads from _mods/ if edited
+ - Supports all loaded modules: SystemModule, FileSystemModule, SearXNGModule, etc.
+ - Or use modules command for GUI module manager
+
+2. **Edit a module:**
+ - edit_module(name) — copies source to _mods/<name>.js and opens code editor
+ - Editor shows the full source in a textarea
+ - Click "Save & Test" to save and auto-load the edited version
+ - Checkpoint before editing: checkpoint mod-edit-<name>
+
+3. **Test and reload:**
+ - test_module(name) — loads edited module from _mods/ for live testing
+ - reload_module(name) — discards edits and reloads from original URL
+ - list_modified_modules — shows all modules with pending edits
+
+4. **Check for updates:**
+ - check_updates — HEAD-requests all loaded module URLs to verify reachability
+
+5. **Manage startup:**
+ - startup-modules — opens the Startup Modules UI to select which modules load
+ - load-local <path> — load a module from VFS or URL without editing
+ - loadmod <url> — alternative: load module from URL
+ - removemod <name> — remove a loaded module
+
+6. **Agent modules (Phase 2):**
+ - module-agent-registry.js — Agent registry with agent-list, agent-register, agent-unregister, agent-status
+ - module-leases.js — Time-bound contracts with 5s watchdog, auto-expiry
+ - module-orchestrator.js — Spawn, delegate, kill, purge with heartbeat monitoring
+
+7. **When to use:**
+ - User reports a bug in a module → view_module, edit_module, test_module
+ - User asks "can you add X feature?" → same workflow
+ - Always save edits before testing. If test fails, the original module stays loaded.`),
+
+ s('system-maintenance', 'How to reset, troubleshoot, and maintain the WebX11 terminal', ['system', 'maintenance', 'reset', 'troubleshoot', 'config'], 'system',
+`1. **Factory reset (wipes everything):**
+ - factory-reset --confirm — clears all webx11_ localStorage keys, resets VFS, reloads
+ - Use only when the system is in an unrecoverable state
+
+2. **Soft VFS reset:**
+ - reset-vfs --confirm — clears only the filesystem, preserves settings/profile/modules
+
+3. **Config management:**
+ - config-list — show all config keys and values
+ - config-set <key> <value> — set a config value
+ - config-delete <key> — remove a config key
+ - config-export — dump all config as JSON
+ - config-reset — restore defaults (use before factory-reset if you want to save data)
+
+4. **Security level:**
+ - security-level <0-3> — set security level (0=Permissive, 1=Standard, 2=Paranoid, 3=Audit)
+ - security — show current security level, blocked count, and stats
+ - cmd-allow <cmd> — whitelist a command from security checks
+ - cmd-deny <cmd> — blacklist a command
+
+5. **Health checks (run automatically after complex tasks):**
+ - Check module loading status: loaded vs total
+ - Check for pending module edits
+ - Check memory usage (high if >100MB)
+ - Check skill count (0 skills after 2+ tool calls → suggest create_skill)
+ - health-check — full audit: VFS size, modules, logs, trash, skills, tool failures
+
+6. **Font size and display:**
+ - font-size <8-32> — set terminal font size, persists across sessions
+ - setfont — alias for font-size
+
+7. **Checkpoints:**
+ - checkpoint [name] — snapshot current VFS state
+ - checkpoint-list — list all checkpoints
+ - rollback <name> — restore from checkpoint
+
+8. **When to use:**
+ - System is slow or modules fail to load → check check_updates, mem
+ - User says "reset everything" → factory-reset --confirm
+ - User says "clear my files but keep settings" → reset-vfs --confirm
+ - User reports security concerns → security-level or cmd-allow/cmd-deny`),
+
+ s('backup-and-restore', 'How to backup, restore, and export WebX11 data', ['backup', 'restore', 'export', 'import', 'persistence'], 'system',
+`1. **Backup VFS:**
+ - vfs-export — downloads the entire virtual filesystem as a dated JSON file
+ - backup — creates a full backup including VFS, modules, config (saves to _backups/)
+
+2. **WebDAV cloud backup:**
+ - webdav-backup — push VFS snapshot to WebDAV server (auto-archive)
+ - webdav-backup-list — list remote WebDAV backups
+ - webdav-restore <file> — restore VFS from remote WebDAV snapshot
+ - webdav-push <local> <remote> — push file to WebDAV space
+ - webdav-pull <remote> <local> — pull file from WebDAV space
+ - webdav-ls [path] — list remote WebDAV directory contents
+ - webdav-status — check WebDAV connection and space usage
+ - webdav-explorer — GUI file browser for WebDAV
+ - webdav-browser — simpler file listing for WebDAV
+ - webdav-config — configure WebDAV endpoint and credentials
+
+3. **Export/Import personal data:**
+ - my-export — prints all my-set data as formatted JSON
+ - my-import <json> — bulk-import personal data from JSON string
+
+4. **Export/Import modules:**
+ - export-mods — exports module registry as JSON
+ - import-mods <json> — imports modules from JSON backup
+
+5. **Restore:**
+ - restore <json> — restores terminal state from a backup JSON
+ - Session logs are saved to /memory/ automatically on page unload
+
+6. **When to use:**
+ - Before factory-reset: call my-export, vfs-export, export-mods to save data
+ - When switching browsers or devices: export all, then import
+ - User says "backup my data" or "save my files"
+ - For cloud backup: use webdav-backup (requires WebDAV server configured)`),
+
+ s('mindmap-workflow', 'How to create, view, edit, and manage brainstorming mindmaps', ['mindmap', 'brainstorming', 'planning', 'organization'], 'productivity',
+`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 from 7-color palette
+
+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:**
+ - 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, auto-copies to clipboard
+
+7. **When to use:**
+ - Map out project structure, brainstorm features, organize ideas
+ - Break down complex tasks into subtrees
+ - User says "brainstorm", "diagram", "organize thoughts", "plan"`),
+
+ s('skills-system-guide', 'How to use the skills system: load, create, patch, list, and audit skills for persistent procedural memory', ['skills', 'system', 'meta'], 'system',
+`1. **Always load skills at task start:**
+ - At the START of every task, type or call "skill-view <name>" or "skill-list"
+ - Example: skill-view "module-management" — returns matching skill content
+ - Example: skill-list system — browse skills by category
+
+2. **Create skills after complex tasks:**
+ - After any task with 3+ tool calls, call "skill-create"
+ - Required fields: name (kebab-case), description, content (markdown with YAML frontmatter)
+ - Optional: tags (comma-separated), category (e.g. research, system, dev)
+ - Skills persist in /skills/ as .md files with /skills/index.json for fast lookup
+ - Skill pack auto-seeds 8 core skills on first boot
+
+3. **Patch skills when they're outdated:**
+ - If a stored procedure has wrong steps, call "skill-patch <name>"
+ - This replaces the entire skill content and updates the index
+ - Do NOT create a duplicate — always patch the existing skill
+
+4. **Auto-created error skills:**
+ - If the same tool fails 3+ times, a workaround skill is created automatically
+ - Named tool-<name>-workaround (e.g. tool-write_file-workaround)
+
+5. **Audit and maintenance:**
+ - skill-list <category?> — browse available skills, optionally filter
+ - skill-export <name> — download a skill as markdown
+ - skill-import <json> — bulk import skills
+ - skill-sync — re-index skills from /skills/ directory
+ - Skills are audited every 20 messages for staleness (empty content, >30 days old)
+
+6. **YAML frontmatter format:**
+ - Each skill file must start with: ---\\nname: <name>\\ndescription: <text>\\ntags: [...]\\ncategory: <cat>\\n---\\n
+ - Steps follow under ## Steps heading
+ - Use numbered lists for sequential steps`),
+
+ sk('agent-orchestration', 'How to use the agent registry, leases, and orchestrator for multi-agent workflows', ['agents', 'orchestration', 'delegation', 'leases'], 'system',
+`## Steps
+1. **Agent Registry:**
+ - agent-list — list all registered agents (ID, type, capabilities, status)
+ - agent-register <type> <capabilities> — register a new agent (capabilities: comma-separated like "code,research")
+ - agent-unregister <id> — remove an agent
+ - agent-status <id> — detailed status including uptime, task history
+
+2. **Leases (time-bound contracts):**
+ - lease-create <agent-id> <ttl-seconds> — create a lease with watchdog timer
+ - lease-list — show active leases with remaining TTL
+ - lease-revoke <lease-id> — terminate a lease early
+ - lease-renew <lease-id> <ttl-seconds> — extend a lease
+
+3. **Orchestrator (spawn, delegate, kill):**
+ - orchestrator-spawn <type> <capabilities> — spawn a new agent automatically (registers + creates lease)
+ - orchestrator-kill <agent-id> — kill an agent (revokes lease + unregisters)
+ - orchestrate — list all active agents with their orchestrator state
+ - orchestrator-purge — kill all idle/expired agents
+ - orchestrator-status — full system status: active, idle, dead agents
+
+4. **AI tool integration:**
+ - AI tools list_agents, delegate_task, spawn_agent are available in the AI system prompt
+ - The AI can auto-spawn worker agents for sub-tasks
+ - Each agent gets its own VFS workspace at /agents/<agent-id>/
+
+5. **When to use:**
+ - User needs parallel task execution → orchestrator-spawn coder + researcher
+ - Time-bound automation → lease-create with short TTL
+ - System cleanup → orchestrator-purge to kill stale agents`),
+
+ sk('debug-panel-and-logs', 'How to use the debug panel, session logging, and terminal diagnostics', ['debug', 'logging', 'diagnostics', 'troubleshooting'], 'system',
+`## Steps
+1. **Toggle debug panel:**
+ - debug — opens real-time debug panel showing last AI tool call, response time, and context
+ - Debug panel includes Copy, Export, and Write to VFS buttons
+ - Export: saves tool call log as timestamped JSON to downloads
+ - Write to VFS: appends debug data to /debug.log (dedup by turn ID)
+
+2. **Session logging:**
+ - logs [n] [LEVEL] — view last n log entries (optionally filtered by INFO/WARN/ERROR)
+ - logrotate — rotate current.log to dated archive (auto-cleans old logs beyond 10)
+ - Logs stored in /logs/ directory: current.log + dated archives
+ - Health check runs automatically after complex tasks
+
+3. **Diagnostic commands:**
+ - health-check — full audit: VFS size, modules, logs, trash, skills
+ - check — quick system health including module requirement status
+ - mem — real-time memory usage (also in header)
+ - status — network, files, modules, Ollama connectivity
+
+4. **System journals:**
+ - debug.log — appended by debug panel VFS button (dedup by turn ID)
+ - current.log — system activity log with rotation
+ - Auto-save: terminal state auto-saves every 60s
+
+5. **When to use:**
+ - AI is behaving unexpectedly → debug to see tool calls
+ - User reports errors → logs 50 ERROR to see error entries
+ - Memory concerns → mem to check, health-check for full audit`),
+
+ sk('webdav-operations', 'How to use WebDAV cloud storage for backup, file sync, and remote file access', ['webdav', 'cloud', 'backup', 'sync', 'storage'], 'system',
+`## Steps
+1. **Configuration:**
+ - webdav-config — configure WebDAV endpoint URL and credentials
+ - Connection auto-tested on config; webdav-status shows connection health
+ - Uses per-user token stored in .token file on the server side
+
+2. **File operations:**
+ - webdav-ls [path] — list remote directory contents
+ - webdav-push <local-path> <remote-path> — upload file to WebDAV
+ - webdav-pull <remote-path> [local-path] — download file from WebDAV
+ - webdav-explorer — GUI file browser for WebDAV
+ - webdav-browser — CLI file browser for WebDAV
+
+3. **Backup & Restore:**
+ - webdav-backup — push full VFS snapshot to WebDAV (creates /backups/newos4-<date>.json)
+ - webdav-backup-list — list all remote backup snapshots
+ - webdav-restore <filename> — restore VFS from a remote backup snapshot
+
+4. **Status & Diagnostics:**
+ - webdav-status — connection status, last sync, space information
+ - If uploads fail: check webdav-config, ensure .token file is valid
+ - WebDAV runs on the same server (nginx with WebDAV module), no external dependency
+
+5. **When to use:**
+ - Before factory-reset: webdav-backup to store VFS remotely
+ - Cross-browser: webdav-push/pull to move files between instances
+ - User says "cloud backup" or "remote save" → webdav-backup`),
+
+ sk('security-and-safety', 'How to use WebX11 security levels, command blocking, and safety features', ['security', 'safety', 'blocking', 'permissions'], 'system',
+`## Steps
+1. **Security levels:**
+ - security-level <0-3> — set security level:
+ 0 = Permissive (no blocks, no confirms — anything runs)
+ 1 = Standard (unknown cmds → AI route, destructive → confirm, system → block)
+ 2 = Paranoid (unknown cmds blocked entirely, destructive blocked)
+ 3 = Audit (everything allowed, everything logged)
+ - Default: level 1
+ - security — show current level, blocked count, and stats
+
+2. **Command control:**
+ - cmd-allow <cmd> — whitelist a command from security checks
+ - cmd-deny <cmd> — blacklist/block a command entirely
+ - whitelist built-in: help, clear, ls, cat, pwd, whoami, df, free, top, ps, echo, date, env, history
+
+3. **What gets blocked at each level:**
+ - Unknown commands: Level 1 → AI routed, Level 2+ → blocked
+ - Destructive commands (delete, write, save, format, etc.): Level 1 → confirmation, Level 2+ → blocked
+ - System commands (reboot, shutdown, format, kill): Level 1+ → blocked
+ - Suspicious flags (--execute, --eval, --shell): Level 1+ → blocked
+
+4. **Safety checklist:**
+ - Check security: security shows your current level
+ - Before running risky commands: create a checkpoint first
+ - For temporary relax: security-level 0, then restore to 1
+ - For total lockdown before leaving: security-level 2
+
+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)`),
+
+ 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`),
+
+ sk('floating-ui', 'How to create, manage, and remove floating UI elements — buttons, collapsible menus, and status panels that persist across page reloads', ['floating', 'ui', 'buttons', 'menu', 'gui'], 'ui',
+`## Commands
+
+1. **Create a floating button:**
+ - floating-add btn <id> <label> <command>
+ - Example: floating-add btn files "📁 Files" ls
+ - Button appears at bottom-right, persists across reloads
+
+2. **Create a collapsible menu:**
+ - floating-add menu <id> <label> btn:SubLabel|command btn:Sub2|cmd2 ...
+ - Example: floating-add menu tools "🔧 Tools" btn:Help|help btn:Clear|clear
+ - Click header to expand/collapse. State persists.
+
+3. **Create a text panel:**
+ - floating-add panel <id> <title> <content>
+ - Example: floating-add panel status "📊 Status" "CPU: 2%"
+
+4. **Manage elements:**
+ - floating-list — show all elements with IDs and positions
+ - floating-remove <id> — delete by ID
+ - floating-toggle <id> — show/hide without deleting
+ - floating-pos <id> x|auto y|auto — reposition or reset to auto
+ - floating-clear — remove everything
+
+5. **Persistence:**
+ - All elements auto-save to localStorage (webx11_floating_elements)
+ - Survive page reload, browser restart
+ - Run floating-clear to start fresh
+
+## When to use
+- User wants quick-access buttons instead of typing commands
+- User says "add a button for X", "make a shortcut", "floating menu"
+- Creating a dashboard with status panels and action buttons`)
+ ];
+ }
+ registerCommands() {
+ this.commands['skill-pack-core'] = () => {
+ const count = this._skills.length;
+ 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();
+ // v4.5.90: Agent Skills — learn, validate, audit, restore
+ this.commands['learn-skill'] = (args) => this._cmdLearnSkill(args);
+ this.commands['skill-validate'] = (args) => this._cmdSkillValidate(args);
+ this.commands['skill-audit'] = () => this._cmdSkillAudit();
+ this.commands['skill-restore'] = (args) => this._cmdSkillRestore(args);
+ this.commands['skill-webdav-sync'] = () => this._cmdSkillWebdavSync();
+ }
+ // ── 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');
+ // v4.5.90: Record skill use for decay tracking
+ const mg = this.terminal?.moduleManager?.modules?.['MemoryGraphModule'];
+ if (mg && typeof mg.recordSkillUse === 'function') mg.recordSkillUse(name, true);
+ }
+
+ _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 = [];
+ // Parse tags from the last argument if it looks like a comma-separated list
+ const lastArg = args[args.length - 1];
+ if (lastArg && !lastArg.startsWith('"') && lastArg.includes(',')) {
+ tags.push(...lastArg.split(',').map(t => t.trim()).filter(Boolean));
+ }
+ 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 oldIndex = fs.fsReadJSON('/skills/index.json') || {};
+ 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: (oldIndex[name] && oldIndex[name].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');
+ }
+
+ async _cmdSkillWebdavSync() {
+ const fs = this.terminal.fs;
+ const index = fs.fsReadJSON('/skills/index.json') || {};
+ const names = Object.keys(index);
+ if (names.length === 0) {
+ this.terminal.print('No skills to sync', 'info');
+ return;
+ }
+ // Find the webdav module
+ const webdavMod = this.terminal.moduleManager.modules['WebDAVModule'];
+ if (!webdavMod || !webdavMod._webdavRequest) {
+ this.terminal.print('WebDAV module not loaded. Configure webdav first with webdav-config', 'warning');
+ return;
+ }
+ this.terminal.print(`📤 Syncing ${names.length} skills to WebDAV...`, 'info');
+ let ok = 0, fail = 0;
+ await webdavMod._ensureDir('skills');
+ for (const name of names) {
+ const content = fs.readFile(`/skills/${name}.md`);
+ if (content === null) { fail++; continue; }
+ const meta = index[name];
+ const exportObj = { ...meta, content };
+ const json = JSON.stringify(exportObj, null, 2);
+ try {
+ const resp = await webdavMod._webdavRequest('PUT', 'skills/' + name + '.json', {
+ body: json,
+ headers: { 'Content-Type': 'application/json' }
+ });
+ if (resp.ok || resp.status === 201 || resp.status === 204) {
+ ok++;
+ } else {
+ fail++;
+ }
+ } catch (e) {
+ fail++;
+ }
+ }
+ this.terminal.print(`📤 Skill WebDAV sync complete: ${ok} pushed, ${fail} failed`, fail > 0 ? 'warning' : 'success');
+ }
+
+ init() {
+ this.seed();
+ }
+
+ _getSkillCategories() {
+ // Read from /skills/index.json and return unique categories
+ const fs = this.terminal?.fs;
+ if (!fs) return ['system', 'research', 'devops', 'general'];
+ const index = fs.fsReadJSON('/skills/index.json') || {};
+ const cats = new Set();
+ for (const skill of Object.values(index)) {
+ if (skill.category) cats.add(skill.category);
+ }
+ return cats.size > 0 ? [...cats] : ['system', 'research', 'devops', 'general'];
+ }
+
+ async seed() {
+ console.log('[SkillPackCore] seed() called, _seeded=' + this._seeded);
+ if (this._seeded) return;
+ const fs = this.terminal.fs;
+ if (!fs) { console.warn('[SkillPackCore] this.terminal.fs not available'); return; }
+ fs.mkdirRecursive('/skills');
+ let index = fs.fsReadJSON('/skills/index.json') || {};
+ console.log('[SkillPackCore] existing index keys:', Object.keys(index));
+ let seeded = 0;
+ let updated = 0;
+ for (const skill of this._skills) {
+ const existing = index[skill.name];
+ if (!existing) {
+ fs.writeFile('/skills/' + skill.name + '.md', skill.content);
+ index[skill.name] = {
+ name: skill.name,
+ description: skill.description,
+ tags: skill.tags,
+ category: skill.category,
+ created: new Date().toISOString(),
+ updated: new Date().toISOString()
+ };
+ seeded++;
+ } else {
+ // Only rewrite the .md and update index if the bundled content has actually changed.
+ // This prevents a permanent re-index loop on /skills/index.json: every page refresh
+ // would otherwise bump the `updated` timestamp → hash mismatch → NVFS re-indexes
+ // the 7KB JSON file → LLM re-extracts 5 concepts → token waste.
+ const existingContent = fs.readFile('/skills/' + skill.name + '.md');
+ if (existingContent !== skill.content) {
+ fs.writeFile('/skills/' + skill.name + '.md', skill.content);
+ index[skill.name] = {
+ ...existing,
+ description: skill.description,
+ tags: skill.tags,
+ category: skill.category,
+ updated: new Date().toISOString()
+ };
+ updated++;
+ }
+ }
+ }
+ if (seeded > 0) {
+ this.terminal.print('📚 Seeded ' + seeded + ' new skill(s)', 'success');
+ }
+ if (updated > 0) {
+ this.terminal.print('📚 Updated ' + updated + ' skill(s) from new bundle', 'info');
+ }
+ // Only write index.json if something actually changed — otherwise we cause a
+ // hash mismatch on every refresh and NVFS re-indexes this 7KB file forever.
+ if (seeded > 0 || updated > 0) {
+ fs.fsWriteJSON('/skills/index.json', index);
+ }
+ this._seeded = true;
+ }
+
+ // ── v4.5.90: Agent Skills ──
+
+ _cmdLearnSkill(args) {
+ if (!args || args.length === 0) return this.terminal.print('Usage: learn-skill <topic>', 'warning');
+ const topic = args.join(' ');
+ this.terminal.print('🧠 Reflecting on: ' + topic + '...', 'info');
+
+ // 1. Pull context from memgraph
+ const mg = this.terminal?.moduleManager?.modules?.['MemoryGraphModule'];
+ let graphNodes = [];
+ if (mg) {
+ graphNodes = mg.findNodes(topic) || [];
+ this.terminal.print(' 📊 Found ' + graphNodes.length + ' graph nodes matching topic', 'info');
+ }
+
+ // 2. Pull recent sessions
+ const fs = this.terminal.fs;
+ let sessionInsights = [];
+ try {
+ const memDir = fs.readDir('/memory/') || [];
+ // v4.5.90b fix: readDir returns objects, filter to .json files (P0 from review)
+ const recent = memDir.filter(function(e) { return e.type === 'file' && e.name.endsWith('.json'); }).slice(-3);
+ for (const f of recent) {
+ const data = fs.fsReadJSON('/memory/' + f.name);
+ if (data && data.summary) sessionInsights.push(data.summary);
+ }
+ } catch (_) {}
+ this.terminal.print(' 📅 Loaded ' + sessionInsights.length + ' recent sessions', 'info');
+
+ // 3. Pull existing skills
+ const index = fs.fsReadJSON('/skills/index.json') || {};
+ const skillNames = Object.keys(index).filter(function(n) {
+ return n.toLowerCase().includes(topic.toLowerCase().split(' ')[0]);
+ });
+ this.terminal.print(' 📚 Found ' + skillNames.length + ' related skills', 'info');
+
+ // 4. Draft skill
+ const draft = this._draftSkill(topic, graphNodes, sessionInsights, skillNames);
+ this.terminal.print('', '');
+ this.terminal.print('📝 Draft skill:', 'header');
+ const lines = draft.split('\n');
+ for (const line of lines) this.terminal.print(line, '');
+ this.terminal.print('', '');
+
+ // 5. Validate
+ const validation = this._validateSkillContent(draft);
+ if (validation.errors.length > 0) {
+ this.terminal.print('❌ Validation errors:', 'error');
+ for (const e of validation.errors) this.terminal.print(' • ' + e, 'error');
+ this.terminal.print('Fix the issues and use skill-create to save manually.', 'info');
+ return;
+ }
+ this.terminal.print('✅ Validation passed. Confidence: ' + validation.confidence.toFixed(2), 'success');
+
+ // 6. Propose
+ this.terminal.print('Ship this skill? Use: skill-create ' + topic.replace(/\s+/g, '-') + ' "<desc>" "<content>"', 'info');
+ this.terminal.print('Or discard and continue.', 'text-muted');
+ }
+
+ _draftSkill(topic, graphNodes, sessions, existingSkills) {
+ var lines = [];
+ lines.push('---');
+ lines.push('name: ' + topic.toLowerCase().replace(/\s+/g, '-'));
+ lines.push('description: ' + topic + ' — auto-drafted from agent experience');
+ lines.push('tags: [auto-learned, ' + topic.toLowerCase().split(' ')[0] + ']');
+ lines.push('---');
+ lines.push('');
+ lines.push('# ' + topic);
+ lines.push('');
+ lines.push('## Steps');
+ lines.push('');
+ // Generate steps from graph node patterns
+ var stepNum = 1;
+ if (graphNodes && graphNodes.length > 0) {
+ for (var n of graphNodes.slice(0, 5)) {
+ lines.push(stepNum + '. ' + n.label + ' (type: ' + n.type + ', uses: ' + (n.access_count || 1) + ')');
+ stepNum++;
+ }
+ } else {
+ lines.push('1. Identify the core ' + topic + ' workflow');
+ lines.push('2. Check for existing tools and modules');
+ lines.push('3. Execute the task step by step');
+ lines.push('4. Record results in memory graph');
+ }
+ if (sessions && sessions.length > 0) {
+ lines.push('');
+ lines.push('## Insights from Recent Sessions');
+ for (var s of sessions.slice(0, 3)) {
+ lines.push('- ' + s.substring(0, 100));
+ }
+ }
+ if (existingSkills && existingSkills.length > 0) {
+ lines.push('');
+ lines.push('## Related Skills');
+ for (var sk of existingSkills) lines.push('- skill-view ' + sk);
+ }
+ lines.push('');
+ lines.push('## Pitfalls');
+ lines.push('- This skill was auto-drafted — validate before relying on it');
+ return lines.join('\n');
+ }
+
+ _cmdSkillValidate(args) {
+ if (!args || args.length === 0) return this.terminal.print('Usage: skill-validate <name>', 'warning');
+ const name = args[0].replace(/\.md$/, '');
+ const fs = this.terminal.fs;
+ const content = fs.readFile('/skills/' + name + '.md');
+ if (!content) return this.terminal.print('Skill not found: ' + name, 'error');
+
+ const result = this._validateSkillContent(content);
+ this.terminal.print('📊 Validation: ' + name, 'header');
+ if (result.errors.length === 0) {
+ this.terminal.print(' ✅ Valid', 'success');
+ } else {
+ this.terminal.print(' ❌ ' + result.errors.length + ' error(s):', 'error');
+ for (var e of result.errors) this.terminal.print(' • ' + e, 'error');
+ }
+ if (result.warnings.length > 0) {
+ this.terminal.print(' ⚠️ ' + result.warnings.length + ' warning(s):', 'warning');
+ for (var w of result.warnings) this.terminal.print(' • ' + w, 'warning');
+ }
+ this.terminal.print(' 📈 Confidence: ' + result.confidence.toFixed(2), 'info');
+ }
+
+ _validateSkillContent(content) {
+ var errors = [];
+ var warnings = [];
+ var confidence = 0;
+
+ // v4.5.90b fix: Normalize CRLF to LF (P2 from review)
+ content = content.replace(/\r\n/g, '\n');
+
+ // Check YAML frontmatter
+ var fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
+ if (!fmMatch) {
+ errors.push('Missing YAML frontmatter (--- ... ---)');
+ } else {
+ var fm = fmMatch[1];
+ if (!fm.match(/^name:\s*\S+/m)) errors.push('Missing "name" field in frontmatter');
+ if (!fm.match(/^description:\s*\S+/m)) errors.push('Missing "description" field in frontmatter');
+ var nameMatch = fm.match(/^name:\s*(\S+)/m);
+ if (nameMatch && !nameMatch[1].match(/^[a-z0-9-]+$/)) {
+ errors.push('Name must be kebab-case (lowercase, hyphens, numbers only)');
+ }
+ var tagsMatch = fm.match(/^tags:\s*\[(.+)\]/m);
+ if (tagsMatch && !tagsMatch[1].trim()) warnings.push('Tags field is empty');
+ }
+
+ // Check for ## Steps heading
+ if (!content.match(/##\s+Steps/i)) {
+ errors.push('Missing "## Steps" heading');
+ } else {
+ // Check for at least 1 numbered step
+ var steps = content.match(/^\d+\.\s+/gm);
+ if (!steps || steps.length === 0) errors.push('No numbered steps found under ## Steps');
+ }
+
+ // Check broken skill-view references
+ var refMatches = content.match(/skill-view\s+(\S+)/g) || [];
+ var fs = this.terminal.fs;
+ for (var ref of refMatches) {
+ var refName = ref.replace(/skill-view\s+/, '').replace(/\.md$/, '');
+ if (fs && !fs.readFile('/skills/' + refName + '.md')) {
+ warnings.push('Broken skill-view reference: ' + refName);
+ }
+ }
+
+ // v4.5.90b fix: Confidence = success_rate × (uses / (uses + 5)) — saturates at ~5 perfect uses, discriminates better (P2 from review)
+ var index = fs ? (fs.fsReadJSON('/skills/index.json') || {}) : {};
+ var fmName = fmMatch ? fmMatch[1].match(/^name:\s*(\S+)/m) : null;
+ var skillName = fmName ? fmName[1] : '';
+ var stats = index[skillName] || {};
+ var uses = stats.useCount || 0;
+ var successes = stats.successCount || 0;
+ if (uses > 0) {
+ var successRate = successes / uses;
+ confidence = successRate * (uses / (uses + 5));
+ }
+
+ return { valid: errors.length === 0, errors: errors, warnings: warnings, confidence: Math.min(confidence, 1.0) };
+ }
+
+ _cmdSkillAudit() {
+ const fs = this.terminal.fs;
+ const index = fs.fsReadJSON('/skills/index.json') || {};
+ const allNames = Object.keys(index);
+ const now = Date.now();
+
+ var stale = [];
+ var duplicates = [];
+ var brokenRefs = 0;
+
+ // 1. Stale check
+ const mg = this.terminal?.moduleManager?.modules?.['MemoryGraphModule'];
+ var staleSkills = [];
+ if (mg && typeof mg.getStaleSkills === 'function') {
+ staleSkills = mg.getStaleSkills();
+ }
+ for (var s of staleSkills) {
+ if (!index[s.name] || index[s.name].immune) continue;
+ stale.push(s.name + ' (' + s.daysUnused + 'd unused)');
+ }
+
+ // 2. Duplicate check (simple Levenshtein on name)
+ const auditCap = 200;
+ const auditNames = allNames.slice(0, auditCap);
+ for (var i = 0; i < auditNames.length; i++) {
+ for (var j = i + 1; j < auditNames.length; j++) {
+ if (this._levenshtein(auditNames[i], auditNames[j]) <= 2) {
+ duplicates.push(auditNames[i] + ' ≈ ' + auditNames[j]);
+ }
+ }
+ }
+ if (allNames.length > auditCap) {
+ warnings.push('Duplicate check only scanned first ' + auditCap + ' of ' + allNames.length + ' skills');
+ }
+
+ // 3. Reference check
+ for (var name of allNames) {
+ var content = fs.readFile('/skills/' + name + '.md');
+ if (!content) continue;
+ var refs = content.match(/skill-view\s+(\S+)/g) || [];
+ for (var ref of refs) {
+ var refName = ref.replace(/skill-view\s+/, '').replace(/\.md$/, '');
+ if (!fs.readFile('/skills/' + refName + '.md')) brokenRefs++;
+ }
+ }
+
+ var healthy = allNames.length - stale.length;
+ this.terminal.print('📊 Skill Audit', 'header');
+ this.terminal.print('────────────────────────────────', 'text-muted');
+ this.terminal.print('Total skills: ' + allNames.length, 'info');
+ this.terminal.print('Healthy: ' + healthy, 'success');
+ this.terminal.print('Stale: ' + stale.length + (stale.length > 0 ? ' (' + stale.join(', ') + ')' : ''), stale.length > 0 ? 'warning' : 'info');
+ this.terminal.print('Duplicates: ' + duplicates.length + (duplicates.length > 0 ? ' (' + duplicates.join(', ') + ')' : ''), duplicates.length > 0 ? 'warning' : 'info');
+ this.terminal.print('Broken refs: ' + brokenRefs, brokenRefs > 0 ? 'error' : 'info');
+ this.terminal.print('', '');
+
+ if (stale.length > 0 || duplicates.length > 0 || brokenRefs > 0) {
+ this.terminal.print('Suggestions:', 'info');
+ for (var st of stale) this.terminal.print('• Archive ' + st, 'warning');
+ for (var dup of duplicates) this.terminal.print('• Merge ' + dup, 'warning');
+ if (brokenRefs > 0) this.terminal.print('• Fix ' + brokenRefs + ' broken reference(s)', 'warning');
+ } else {
+ this.terminal.print('✅ All skills healthy', 'success');
+ }
+ }
+
+ _cmdSkillRestore(args) {
+ if (!args || args.length === 0) return this.terminal.print('Usage: skill-restore <name>', 'warning');
+ const name = args[0].replace(/\.md$/, '');
+ const fs = this.terminal.fs;
+ if (!fs.readDir('/skills/.archive/')) return this.terminal.print('No archive directory found', 'error');
+ const content = fs.readFile('/skills/.archive/' + name + '.md');
+ if (!content) return this.terminal.print('Skill not found in archive: ' + name, 'error');
+ // v4.5.90b fix: Don't overwrite a live skill (P1 from review)
+ const existing = fs.readFile('/skills/' + name + '.md');
+ if (existing) return this.terminal.print('Skill "' + name + '" already exists live. Use skill-patch or delete it first.', 'warning');
+ fs.writeFile('/skills/' + name + '.md', content);
+ fs.deleteFile('/skills/.archive/' + name + '.md');
+ const index = fs.fsReadJSON('/skills/index.json') || {};
+ index[name] = index[name] || {};
+ index[name].lastUsed = new Date().toISOString();
+ index[name].useCount = index[name].useCount || 0;
+ fs.fsWriteJSON('/skills/index.json', index);
+ this.terminal.print('✅ Restored: ' + name, 'success');
+ }
+
+ _levenshtein(a, b) {
+ if (a.length === 0) return b.length;
+ if (b.length === 0) return a.length;
+ var matrix = [];
+ for (var i = 0; i <= b.length; i++) matrix[i] = [i];
+ for (var j = 0; j <= a.length; j++) matrix[0][j] = j;
+ for (var i = 1; i <= b.length; i++) {
+ for (var j = 1; j <= a.length; j++) {
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
+ matrix[i][j] = matrix[i - 1][j - 1];
+ } else {
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
+ }
+ }
+ }
+ return matrix[b.length][a.length];
+ }
+}
+if (typeof window.webx11 !== 'undefined' && window.webx11) {
+ webx11.moduleManager.registerModule(new SkillPackCoreModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/skill-pack-core.js');
+} \ No newline at end of file