aboutsummaryrefslogtreecommitdiff
path: root/modules.html
diff options
context:
space:
mode:
Diffstat (limited to 'modules.html')
-rw-r--r--modules.html386
1 files changed, 386 insertions, 0 deletions
diff --git a/modules.html b/modules.html
new file mode 100644
index 0000000..521f92f
--- /dev/null
+++ b/modules.html
@@ -0,0 +1,386 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+<meta name="theme-color" content="#0a0a14">
+<title>WebX11 — Modules</title>
+<script>!function(){var v=localStorage.getItem('webx11_cache_ver');if(!v)return;var m=v.match(/v([\d.]+)/);if(!m)return;document.title='WebX11 v'+m[1]+' — Modules';document.querySelector('.badge-v')&&(document.querySelector('.badge-v').textContent='v'+m[1])}()</script>
+<style>
+*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
+:root{--bg:#0a0a14;--bg2:#141428;--bg3:#1c1c36;--bg4:#24244a;--text:#e8e8ff;--text2:#b0b0cc;--accent:#818cf8;--accent2:#a78bfa;--green:#34d399;--cyan:#22d3ee;--amber:#fbbf24;--rose:#fb7185;--pink:#e879f9;--border:#3a3a5a;--radius:12px;--font-mono:'JetBrains Mono','Fira Code','Cascadia Code','Courier New',monospace;--font-sans:-apple-system,BlinkMacSystemFont,'Segoe UI','Inter','SF Pro',Roboto,sans-serif}
+body{background:var(--bg);color:var(--text);font-family:var(--font-sans);line-height:1.6;padding:0 16px 80px;max-width:1100px;margin:0 auto;min-height:100vh}
+header{padding:24px 0 16px;border-bottom:1px solid var(--border);margin-bottom:20px;display:flex;align-items:center;gap:12px;flex-wrap:wrap}
+h1{font-size:1.5rem;font-weight:600;letter-spacing:-0.02em}
+.badge-v{background:var(--bg4);color:var(--accent);font-family:var(--font-mono);font-size:.75rem;padding:2px 10px;border-radius:20px;border:1px solid var(--border)}
+.live-badge{font-size:.72rem;padding:3px 10px;border-radius:12px;font-family:var(--font-mono);border:1px solid var(--border);cursor:pointer;transition:all .2s;background:var(--bg3);color:var(--text2)}
+.live-badge.active{background:#0d3320;border-color:var(--green);color:var(--green)}
+.live-badge.connecting{background:#332a0d;border-color:var(--amber);color:var(--amber)}
+.toolbar{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;align-items:center}
+#search{flex:1;min-width:160px;background:var(--bg2);border:1px solid var(--border);border-radius:8px;padding:10px 14px;color:var(--text);font-size:.9rem;font-family:var(--font-sans);outline:none;transition:border-color .2s}
+#search:focus{border-color:var(--accent)}
+#search::placeholder{color:var(--text2)}
+.tabs{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:12px}
+.tab{background:var(--bg3);color:var(--text2);border:1px solid var(--border);border-radius:8px;padding:6px 14px;font-size:.82rem;cursor:pointer;transition:all .2s;font-family:inherit}
+.tab:hover{border-color:var(--accent);color:var(--text)}
+.tab.active{background:var(--accent);color:#fff;border-color:var(--accent)}
+.stats{color:var(--text2);font-size:.82rem;margin-bottom:12px;display:flex;gap:16px;flex-wrap:wrap}
+.module-row{background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);margin-bottom:6px;overflow:hidden}
+.module-header{display:flex;align-items:center;gap:10px;padding:12px 14px;cursor:pointer;transition:background .2s;-webkit-user-select:none;user-select:none}
+.module-header:hover{background:var(--bg3)}
+.module-header .name{font-size:.92rem;font-weight:500;flex:1}
+.module-header .class-name{color:var(--text2);font-size:.78rem;font-family:var(--font-mono)}
+.badge{font-size:.68rem;padding:2px 8px;border-radius:8px;font-family:var(--font-mono);white-space:nowrap}
+.badge-core{background:var(--bg4);color:var(--accent);border:1px solid var(--accent)}
+.badge-ext{background:#1a2a1a;color:var(--green);border:1px solid var(--green)}
+.badge-infra{background:#1a1a2a;color:var(--cyan);border:1px solid var(--cyan)}
+.badge-unknown{background:#2a1a1a;color:var(--rose);border:1px solid var(--rose)}
+.status-dot{width:8px;height:8px;border-radius:50%;display:inline-block;flex-shrink:0}
+.status-loaded{background:var(--green);box-shadow:0 0 4px var(--green)}
+.status-unloaded{background:var(--text2)}
+.status-modified{background:var(--amber);box-shadow:0 0 4px var(--amber)}
+.expand-icon{color:var(--text2);font-size:.7rem;transition:transform .2s}
+.expand-icon.open{transform:rotate(90deg)}
+.module-detail{display:none;padding:0 14px 14px;border-top:1px solid var(--border)}
+.module-detail.open{display:block}
+.detail-grid{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;font-size:.82rem;margin-bottom:10px;margin-top:10px}
+.dt{color:var(--text2);white-space:nowrap}
+.dd{color:var(--text)}
+.dd .cmd-tag{display:inline-block;background:var(--bg3);padding:2px 8px;border-radius:6px;margin:2px 2px;font-family:var(--font-mono);font-size:.75rem;color:var(--cyan)}
+.dd .cmd-tag.missing{color:var(--rose);opacity:.7}
+.dd .cmd-tag.loaded{color:var(--green)}
+.dd a{color:var(--accent);text-decoration:none}
+.dd a:hover{text-decoration:underline}
+.cmd-section{margin-top:8px}
+.cmd-section h4{font-size:.82rem;font-weight:500;color:var(--text2);margin-bottom:4px}
+.command-bar{display:flex;flex-wrap:wrap;gap:4px}
+.error-banner{background:#4a1a1a;border:1px solid var(--rose);border-radius:var(--radius);padding:16px;text-align:center;color:var(--rose);margin-bottom:16px}
+.error-banner button{background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 20px;margin-top:8px;cursor:pointer;font-family:inherit}
+.empty-state{text-align:center;color:var(--text2);padding:40px 20px;font-size:.9rem}
+#loading{text-align:center;color:var(--text2);padding:40px}
+.live-indicator{font-size:.75rem;display:flex;align-items:center;gap:4px;padding:2px 8px;border-radius:10px}
+.live-connected{color:var(--green)}
+.live-offline{color:var(--text2)}
+footer{margin-top:40px;padding-top:16px;border-top:1px solid var(--border);text-align:center;font-size:.8rem;color:var(--text2)}
+footer a{color:var(--accent);text-decoration:none}
+.hidden{display:none!important}
+@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
+.connecting .live-indicator{animation:pulse 1.2s ease-in-out infinite}
+@media(max-width:600px){.module-header{flex-wrap:wrap;gap:6px}body{padding:0 12px 60px}}
+</style>
+</head>
+<body>
+<header>
+ <h1>🧩 <span class="badge-v"></span> Modules</h1>
+ <span class="live-badge" id="liveBtn" onclick="toggleLive()">⚡ Offline</span>
+</header>
+<div class="tabs" id="tabs"></div>
+<div class="toolbar">
+ <input type="text" id="search" placeholder="Search modules by name, class, command, description...">
+</div>
+<div class="stats" id="stats"></div>
+<div id="loading">Loading module index...</div>
+<div id="error" class="error-banner hidden">
+ <div id="errorMsg"></div>
+ <button onclick="loadModules()">Retry</button>
+</div>
+<div id="empty" class="empty-state hidden">No modules match your filters.</div>
+<div id="moduleList"></div>
+<footer><a href="skills.html">Skills</a> · <a href="help.html">Help</a> · <a href="guide.html">Guide</a></footer>
+<script>
+const INDEX_URL = '/spaces/newos4/modules-index.json';
+const PKGS_URL = '/spaces/newos4/packages.json';
+let allModules = [];
+let packages = {};
+let liveState = {};
+let currentTab = 'all';
+let liveMode = false;
+
+function getCacheVer() { return localStorage.getItem('webx11_cache_ver') || ''; }
+
+function groupByType(mods) {
+ const groups = { core: [], ext: [], infra: [], unknown: [] };
+ mods.forEach(m => {
+ const t = (m.type || 'unknown').toLowerCase();
+ const l = (m.layer || '').toLowerCase();
+ if (l === 'infrastructure') groups.infra.push(m);
+ else if (t === 'core') groups.core.push(m);
+ else if (t === 'ext') groups.ext.push(m);
+ else groups.unknown.push(m);
+ });
+ return groups;
+}
+
+function getSortedModules(mods) {
+ return mods.sort((a,b) => {
+ const aName = a.name || a.class || '';
+ const bName = b.name || b.class || '';
+ return aName.localeCompare(bName);
+ });
+}
+
+async function loadModules() {
+ const list = document.getElementById('moduleList');
+ const loading = document.getElementById('loading');
+ const error = document.getElementById('error');
+ loading.classList.remove('hidden');
+ error.classList.add('hidden');
+ list.innerHTML = '';
+
+ try {
+ const [idxResp, pkgResp] = await Promise.all([
+ fetch(INDEX_URL + '?v=' + getCacheVer()),
+ fetch(PKGS_URL + '?v=' + getCacheVer()).catch(() => null)
+ ]);
+ if (!idxResp.ok) throw new Error('HTTP ' + idxResp.status);
+ const idx = await idxResp.json();
+ if (pkgResp && pkgResp.ok) {
+ packages = (await pkgResp.json()).packages || {};
+ }
+
+ allModules = [];
+ for (const [key, meta] of Object.entries(idx.modules || {})) {
+ const pkgInfo = packages[key] || {};
+ allModules.push({
+ key: key,
+ name: meta.name || key,
+ file: meta.file || '',
+ type: meta.type || 'unknown',
+ class: meta.class || '',
+ auto_load: meta.auto_load,
+ commands: meta.commands || [],
+ description: meta.description || pkgInfo.desc || '',
+ dependencies: meta.dependencies || [],
+ version: meta.version || '',
+ updated: meta.updated || '',
+ layer: meta.layer || '',
+ pkgName: pkgInfo.name || '',
+ pkgCategory: pkgInfo.category || ''
+ });
+ }
+
+ renderTabs();
+ applyFilters();
+ loading.classList.add('hidden');
+ } catch(e) {
+ loading.classList.add('hidden');
+ document.getElementById('errorMsg').textContent = 'Failed to load modules: ' + e.message;
+ error.classList.remove('hidden');
+ }
+}
+
+function renderTabs() {
+ const groups = groupByType(allModules);
+ const counts = { all: allModules.length, core: groups.core.length, ext: groups.ext.length, infra: groups.infra.length, unknown: groups.unknown.length };
+ const container = document.getElementById('tabs');
+ container.innerHTML = ['all', 'core', 'ext', 'infra', 'unknown'].map(t =>
+ `<button class="tab${t === currentTab ? ' active' : ''}" data-tab="${t}" onclick="setTab('${t}')">${t} (${counts[t]})</button>`
+ ).join('');
+}
+
+function setTab(tab) {
+ currentTab = tab;
+ document.querySelectorAll('.tab').forEach(b => b.classList.toggle('active', b.dataset.tab === tab));
+ applyFilters();
+}
+
+function applyFilters() {
+ const q = document.getElementById('search').value.toLowerCase().trim();
+ let filtered = allModules;
+
+ if (currentTab !== 'all') {
+ const isInfra = currentTab === 'infra';
+ filtered = filtered.filter(m => {
+ const ml = (m.layer || '').toLowerCase();
+ const mt = (m.type || '').toLowerCase();
+ if (isInfra) return ml === 'infrastructure';
+ return mt === currentTab && ml !== 'infrastructure';
+ });
+ }
+
+ if (q) {
+ filtered = filtered.filter(m =>
+ (m.name || '').toLowerCase().includes(q) ||
+ (m.class || '').toLowerCase().includes(q) ||
+ (m.description || '').toLowerCase().includes(q) ||
+ (m.commands || []).some(c => c.toLowerCase().includes(q)) ||
+ (m.key || '').toLowerCase().includes(q)
+ );
+ }
+
+ filtered = getSortedModules(filtered);
+ renderList(filtered);
+}
+
+function renderList(modules) {
+ const container = document.getElementById('moduleList');
+ const stats = document.getElementById('stats');
+ const empty = document.getElementById('empty');
+
+ stats.textContent = modules.length + ' of ' + allModules.length + ' modules';
+ if (liveMode) {
+ const liveCount = Object.keys(liveState).length;
+ stats.textContent += ' · ⚡ Live: ' + liveCount + ' running instances';
+ }
+
+ if (modules.length === 0) {
+ container.innerHTML = '';
+ empty.classList.remove('hidden');
+ return;
+ }
+ empty.classList.add('hidden');
+
+ container.innerHTML = modules.map((m, idx) => {
+ const isLive = liveMode && liveState[m.class];
+ const liveInfo = isLive ? liveState[m.class] : null;
+ const statusClass = liveInfo ? (liveInfo.modified ? 'status-modified' : 'status-loaded') : 'status-unloaded';
+ const badgeType = (m.layer || '').toLowerCase() === 'infrastructure' ? 'infra' : (m.type || 'unknown');
+ const badgeClass = 'badge-' + badgeType;
+ const filePath = m.file ? '/spaces/newos4/' + m.file : '';
+ const cmdCount = m.commands.length;
+ const cmdList = m.commands.slice(0, 8);
+ const cmdExtra = cmdCount - 8;
+
+ return `<div class="module-row">
+ <div class="module-header" onclick="toggleDetail(this)">
+ <span class="status-dot ${statusClass}" title="${liveInfo ? (liveInfo.modified ? 'Has local edits' : 'Loaded') : 'Not loaded'}"></span>
+ <span class="name">${m.name}</span>
+ <span class="class-name">${m.class || m.key}</span>
+ <span class="badge ${badgeClass}">${badgeType}</span>
+ <span class="expand-icon">▶</span>
+ </div>
+ <div class="module-detail" id="detail-${idx}">
+ <div class="detail-grid">
+ <span class="dt">Commands</span>
+ <span class="dd">${cmdCount} ${cmdCount === 1 ? 'command' : 'commands'}</span>
+
+ ${m.description ? '<span class="dt">Desc</span><span class="dd">' + m.description + '</span>' : ''}
+ ${m.file ? '<span class="dt">File</span><span class="dd"><a href="' + filePath + '" target="_blank">' + m.file + '</a></span>' : ''}
+ <span class="dt">Class</span><span class="dd">${m.class || '—'}</span>
+ ${m.version ? '<span class="dt">Version</span><span class="dd">' + m.version + '</span>' : ''}
+ <span class="dt">Auto-load</span><span class="dd">${m.auto_load ? '✅ Yes' : '❌ No'}</span>
+ ${m.dependencies && m.dependencies.length ? '<span class="dt">Deps</span><span class="dd">' + m.dependencies.join(', ') + '</span>' : ''}
+ ${liveInfo ? '<span class="dt">Status</span><span class="dd">' + (liveInfo.modified ? '🟡 Modified locally' : '✅ Loaded') + '</span>' : ''}
+ ${liveInfo && liveInfo.commands ? '<span class="dt">Live</span><span class="dd">' + liveInfo.commands.length + ' active commands</span>' : ''}
+ </div>
+ <div class="cmd-section">
+ <h4>Commands</h4>
+ <div class="command-bar">${m.commands.map(c => {
+ const liveAvailable = liveInfo && liveInfo.commands && liveInfo.commands.includes(c);
+ const cls = liveMode ? (liveAvailable ? 'cmd-tag loaded' : 'cmd-tag missing') : 'cmd-tag';
+ return '<span class="' + cls + '">' + c + '</span>';
+ }).join('')}</div>
+ </div>
+ </div>
+ </div>`;
+ }).join('');
+}
+
+function toggleDetail(el) {
+ const detail = el.nextElementSibling;
+ const icon = el.querySelector('.expand-icon');
+ if (!detail) return;
+ detail.classList.toggle('open');
+ icon.classList.toggle('open');
+}
+
+async function toggleLive() {
+ const btn = document.getElementById('liveBtn');
+ if (liveMode) {
+ liveMode = false;
+ btn.textContent = '⚡ Offline';
+ btn.classList.remove('active', 'connecting');
+ liveState = {};
+ applyFilters();
+ return;
+ }
+
+ btn.textContent = '⏳ Connecting...';
+ btn.classList.add('connecting');
+
+ liveState = {};
+ // Try 3 approaches to get live state
+ try {
+ // Approach 1: parent window (if loaded in iframe)
+ if (window.parent && window.parent !== window && window.parent.webx11) {
+ const mm = window.parent.webx11.moduleManager;
+ if (mm && mm.modules) {
+ for (const [className, instance] of mm.modules) {
+ const cmds = instance.commands ? Object.keys(instance.commands) : [];
+ const modified = !!(window.parent.webx11.terminal && window.parent.webx11.terminal.fs
+ && window.parent.webx11.terminal.fs.readFile('_mods/' + className + '.js'));
+ liveState[className] = { commands: cmds, modified: !!modified, count: cmds.length };
+ }
+ }
+ }
+ // Approach 2: direct window (if somehow same-context)
+ if (Object.keys(liveState).length === 0 && window.webx11) {
+ const mm = window.webx11.moduleManager;
+ if (mm && mm.modules) {
+ for (const [className, instance] of mm.modules) {
+ const cmds = instance.commands ? Object.keys(instance.commands) : [];
+ const modified = !!(window.webx11.terminal && window.webx11.terminal.fs
+ && window.webx11.terminal.fs.readFile('_mods/' + className + '.js'));
+ liveState[className] = { commands: cmds, modified: !!modified, count: cmds.length };
+ }
+ }
+ }
+ } catch(e) { console.warn('Live mode probe failed:', e); }
+
+ const count = Object.keys(liveState).length;
+ if (count > 0) {
+ liveMode = true;
+ btn.textContent = '⚡ Live (' + count + ')';
+ btn.classList.remove('connecting');
+ btn.classList.add('active');
+ } else {
+ btn.textContent = '⚡ Offline (no terminal)';
+ btn.classList.remove('connecting');
+ // Try approach 3: open an iframe to probe
+ tryProbeIFrame(btn);
+ return;
+ }
+ applyFilters();
+}
+
+function tryProbeIFrame(btn) {
+ // Approach 3: create hidden iframe with main.html and wait for ready
+ const iframe = document.createElement('iframe');
+ iframe.style.cssText = 'position:fixed;left:-9999px;top:0;width:1px;height:1px;border:none';
+ iframe.src = '/spaces/newos4/main.html?embed=1';
+ iframe.onload = function() {
+ try {
+ const mm = iframe.contentWindow?.webx11?.moduleManager;
+ if (mm && mm.modules) {
+ for (const [className, instance] of mm.modules) {
+ const cmds = instance.commands ? Object.keys(instance.commands) : [];
+ liveState[className] = { commands: cmds, count: cmds.length };
+ }
+ }
+ } catch(e) { /* cross-origin or timeout */ }
+ document.body.removeChild(iframe);
+ const count = Object.keys(liveState).length;
+ if (count > 0) {
+ liveMode = true;
+ btn.textContent = '⚡ Live (' + count + ')';
+ btn.classList.remove('connecting');
+ btn.classList.add('active');
+ applyFilters();
+ } else {
+ btn.textContent = '⚡ Offline';
+ btn.classList.remove('connecting');
+ }
+ };
+ iframe.onerror = function() {
+ document.body.removeChild(iframe);
+ btn.textContent = '⚡ Offline';
+ btn.classList.remove('connecting');
+ };
+ document.body.appendChild(iframe);
+}
+
+document.getElementById('search').addEventListener('input', applyFilters);
+document.addEventListener('DOMContentLoaded', loadModules);
+</script>
+</body>
+</html> \ No newline at end of file