aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/course/event.php40
-rw-r--r--course.html252
-rw-r--r--course/atlas.json3768
-rw-r--r--course/changelog.jsonl0
-rw-r--r--course/chapters/01-orientation.json132
-rw-r--r--course/chapters/02-architecture.json131
-rw-r--r--course/chapters/03-commands.json119
-rw-r--r--course/chapters/04-ai.json128
-rw-r--r--course/chapters/05-modules.json115
-rw-r--r--course/chapters/06-files-cloud.json132
-rw-r--r--course/chapters/07-automation.json134
-rw-r--r--course/chapters/08-build.json117
-rw-r--r--course/course.json156
-rw-r--r--course/faq.json3
-rw-r--r--course/feed.json4
-rw-r--r--course/projects/daily-digest.json38
-rw-r--r--course/projects/notes-bot.json39
-rw-r--r--course/projects/sync-backup.json38
-rw-r--r--main.html12
-rw-r--r--modules-index.json1997
-rw-r--r--modules/core/core-bundle.js12
-rw-r--r--modules/core/module-loader.js4
-rw-r--r--modules/core/terminal.js12
-rw-r--r--modules/ext/course.js323
-rw-r--r--packages.json32
-rw-r--r--scripts/course-daily-update.py138
-rw-r--r--scripts/course-insights.py57
-rw-r--r--scripts/course-validate.py95
-rw-r--r--scripts/gen-course-atlas.py113
-rw-r--r--scripts/newos4-maintain.py118
-rw-r--r--scripts/test-course.mjs59
31 files changed, 7391 insertions, 927 deletions
diff --git a/api/course/event.php b/api/course/event.php
new file mode 100644
index 0000000..696d1c5
--- /dev/null
+++ b/api/course/event.php
@@ -0,0 +1,40 @@
+<?php
+// api/course/event.php — anonymous course telemetry. No IP/UA stored.
+header('Content-Type: application/json; charset=utf-8');
+header('Access-Control-Allow-Origin: https://webx11.duckdns.org');
+header('Access-Control-Allow-Methods: POST, OPTIONS');
+header('Access-Control-Allow-Headers: Content-Type');
+if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
+if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['error' => 'POST only']); exit; }
+
+$raw = file_get_contents('php://input');
+if ($raw === false || strlen($raw) === 0) { http_response_code(400); echo json_encode(['error' => 'empty body']); exit; }
+if (strlen($raw) > 4096) { http_response_code(413); echo json_encode(['error' => 'payload too large']); exit; }
+$p = json_decode($raw, true);
+if (!is_array($p)) { http_response_code(400); echo json_encode(['error' => 'invalid json']); exit; }
+
+$sid = isset($p['sid']) ? substr(preg_replace('/[^a-zA-Z0-9_-]/', '', (string)$p['sid']), 0, 64) : '';
+$type = isset($p['type']) ? (string)$p['type'] : '';
+$allowed = ['lesson_open','lesson_complete','exercise_fail','question','project_open','project_view','quiz_answer'];
+if (strlen($sid) < 8 || !in_array($type, $allowed, true)) { http_response_code(400); echo json_encode(['error' => 'sid/type invalid']); exit; }
+
+$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
+$dir = __DIR__ . '/../../data/course';
+if (!is_dir($dir)) { mkdir($dir, 0775, true); }
+$rlFile = $dir . '/.rl-' . substr(hash('sha256', $ip), 0, 16) . '-' . date('YmdH');
+$hits = file_exists($rlFile) ? (int)file_get_contents($rlFile) : 0;
+if ($hits > 120) { http_response_code(429); echo json_encode(['error' => 'rate limited']); exit; }
+file_put_contents($rlFile, (string)($hits + 1), LOCK_EX);
+if (random_int(1, 50) === 1) {
+ foreach (glob($dir . '/.rl-*') as $f) { if (filemtime($f) < time() - 172800) @unlink($f); }
+}
+
+$detail = isset($p['detail']) && is_array($p['detail']) ? array_slice($p['detail'], 0, 8, true) : [];
+foreach ($detail as $k => $v) { if (is_string($v)) $detail[$k] = substr($v, 0, 300); }
+$row = ['ts' => gmdate('c'), 'type' => $type, 'sid' => $sid,
+ 'version' => substr((string)($p['version'] ?? ''), 0, 40), 'detail' => $detail];
+$out = $dir . '/events-' . date('Ym') . '.jsonl';
+$fp = fopen($out, 'a');
+if ($fp && flock($fp, LOCK_EX)) { fwrite($fp, json_encode($row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n"); flock($fp, LOCK_UN); fclose($fp); }
+http_response_code(200);
+echo json_encode(['ok' => true]);
diff --git a/course.html b/course.html
new file mode 100644
index 0000000..c61a901
--- /dev/null
+++ b/course.html
@@ -0,0 +1,252 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="theme-color" content="#0a0a14">
+<title>newOS4 — Interactive Course</title>
+<style>
+:root{--bg:#0a0a14;--bg2:#141428;--bg3:#1c1c36;--text:#e8e8ff;--text2:#b0b0cc;--accent:#818cf8;--accent2:#a78bfa;
+--green:#34d399;--cyan:#22d3ee;--amber:#fbbf24;--rose:#fb7185;--border:#3a3a5a;--radius:12px;
+--mono:'JetBrains Mono','Fira Code',monospace;--sans:-apple-system,BlinkMacSystemFont,'Segoe UI',Inter,Roboto,sans-serif}
+*{box-sizing:border-box;margin:0;padding:0}
+body{background:radial-gradient(1200px 600px at 20% -10%,#1b1b3a 0,var(--bg) 55%);color:var(--text);font-family:var(--sans);min-height:100vh}
+a{color:var(--accent)} code,pre{font-family:var(--mono)}
+header{position:sticky;top:0;z-index:20;display:flex;gap:10px;align-items:center;padding:12px 20px;background:rgba(10,10,20,.86);backdrop-filter:blur(14px);border-bottom:1px solid var(--border);flex-wrap:wrap}
+header h1{font-size:1.05rem;letter-spacing:.3px}
+.chip{font-size:.72rem;padding:3px 9px;border:1px solid var(--border);border-radius:999px;color:var(--text2)}
+.btn{background:var(--bg3);border:1px solid var(--border);color:var(--text);border-radius:9px;padding:7px 12px;cursor:pointer;font-size:.82rem;text-decoration:none;display:inline-block}
+.btn:hover{border-color:var(--accent);color:#fff}
+.btn.primary{background:linear-gradient(135deg,var(--accent),var(--accent2));border:0;color:#0b0b18;font-weight:700}
+.grow{flex:1}
+.layout{display:grid;grid-template-columns:280px minmax(0,1fr) 300px;gap:18px;padding:18px;max-width:1500px;margin:0 auto}
+@media(max-width:1050px){.layout{grid-template-columns:1fr}.rail{display:none}}
+.panel{background:rgba(20,20,40,.72);border:1px solid var(--border);border-radius:var(--radius);padding:14px}
+.side h3,.rail h3{font-size:.75rem;text-transform:uppercase;letter-spacing:.12em;color:var(--text2);margin-bottom:10px}
+.chapter{display:flex;justify-content:space-between;gap:8px;padding:8px 10px;border-radius:9px;cursor:pointer;font-size:.85rem}
+.chapter:hover,.chapter.active{background:var(--bg3)}
+.chapter small{color:var(--text2)}
+.lesson{padding:6px 10px 6px 22px;font-size:.8rem;color:var(--text2);cursor:pointer;border-left:2px solid transparent}
+.lesson:hover{color:var(--text)} .lesson.done{color:var(--green);border-left-color:var(--green)}
+.bar{height:8px;background:var(--bg3);border-radius:99px;overflow:hidden}
+.bar>i{display:block;height:100%;width:0;background:linear-gradient(90deg,var(--accent),var(--cyan));transition:width .3s}
+main h2{font-size:1.35rem;margin-bottom:6px}
+.meta{color:var(--text2);font-size:.82rem;margin-bottom:16px}
+.block{margin:12px 0;line-height:1.65}
+.block.cmd{background:#0d0d1c;border:1px solid var(--border);border-left:3px solid var(--green);border-radius:8px;padding:10px 12px;font-family:var(--mono);color:#b8f5d8;display:flex;justify-content:space-between;gap:10px;align-items:center;flex-wrap:wrap}
+.block.tip,.block.warn{border-radius:8px;padding:10px 12px;font-size:.9rem}
+.block.tip{background:rgba(52,211,153,.08);border:1px solid rgba(52,211,153,.3)}
+.block.warn{background:rgba(251,113,133,.08);border:1px solid rgba(251,113,133,.35)}
+.block.table{background:rgba(34,211,238,.06);border:1px solid rgba(34,211,238,.25);border-radius:8px;padding:10px 12px;font-size:.85rem}
+.exercise{margin-top:18px;background:rgba(129,140,248,.07);border:1px solid rgba(129,140,248,.35);border-radius:10px;padding:14px}
+.quiz{margin-top:14px;border-top:1px dashed var(--border);padding-top:14px}
+.opt{display:block;width:100%;text-align:left;margin:6px 0;padding:8px 10px;background:var(--bg3);border:1px solid var(--border);border-radius:8px;color:var(--text);cursor:pointer;font-size:.85rem}
+.opt:hover{border-color:var(--accent)}
+.rail .search{width:100%;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:9px;color:var(--text);font-size:.82rem}
+.atlas-item{padding:7px 0;border-bottom:1px solid rgba(58,58,90,.4);font-size:.8rem}
+.atlas-item code{color:var(--cyan)} .atlas-item span{color:var(--text2);display:block;margin-top:2px}
+#tutor-log{max-height:260px;overflow:auto;font-size:.8rem;color:var(--text2);margin:8px 0}
+.small{font-size:.75rem;color:var(--text2)}
+</style>
+</head>
+<body>
+<header>
+ <h1>📚 newOS4 <span style="color:var(--accent)">Course</span></h1>
+ <span class="chip" id="ver">loading…</span>
+ <span class="chip" id="xp">0 XP</span>
+ <div class="grow"></div>
+ <button class="btn" onclick="showProjects()">Projects</button>
+ <button class="btn" onclick="showFeed()">Daily</button>
+ <a class="btn" href="main.html">⌨ Terminal</a>
+ <a class="btn" href="help.html">Help</a>
+ <a class="btn" href="modules.html">Modules</a>
+</header>
+<div class="layout">
+ <aside class="panel side">
+ <h3>Curriculum</h3>
+ <div class="bar" style="margin-bottom:12px"><i id="pbar"></i></div>
+ <div id="chapters"><p class="small">Loading…</p></div>
+ <div style="margin-top:14px;border-top:1px solid var(--border);padding-top:10px">
+ <label class="small" style="display:flex;gap:6px;align-items:center;cursor:pointer">
+ <input type="checkbox" id="telemetry" onchange="setTelemetry(this.checked)"> Share anonymous progress
+ </label>
+ <button class="btn" style="width:100%;margin-top:8px" onclick="exportProgress()">Export progress</button>
+ </div>
+ </aside>
+ <main class="panel" id="reader"><p class="small">Loading course…</p></main>
+ <aside class="panel rail">
+ <h3>Command atlas</h3>
+ <input class="search" id="atlas-q" placeholder="filter 344 commands…" autocomplete="off">
+ <div id="atlas" style="margin-top:8px;max-height:400px;overflow:auto"></div>
+ <h3 style="margin-top:16px">Ask the tutor</h3>
+ <div id="tutor-log"></div>
+ <input class="search" id="tutor-q" placeholder="ask about this lesson…" autocomplete="off">
+ <button class="btn primary" style="width:100%;margin-top:8px" id="tutor-send">Ask AI tutor</button>
+ </aside>
+</div>
+<script>
+const BASE = '/spaces/newos4/course/';
+const KEY = 'webx11_course_progress_v1';
+const V = Date.now().toString(36);
+let MANIFEST = null, ATLAS = null, ACTIVE = null;
+const $ = id => document.getElementById(id);
+const esc = s => String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
+const prog = () => { try { return JSON.parse(localStorage.getItem(KEY)) || {completed:{},xp:0}; } catch(e){ return {completed:{},xp:0}; } };
+const save = p => localStorage.setItem(KEY, JSON.stringify(p));
+
+async function boot(){
+ try{
+ const [m,a] = await Promise.all([
+ fetch(BASE+'course.json?v='+V).then(r=>r.json()),
+ fetch(BASE+'atlas.json?v='+V).then(r=>r.json()).catch(()=>null)
+ ]);
+ MANIFEST = m; ATLAS = a;
+ $('ver').textContent = 'v' + m.version;
+ $('telemetry').checked = localStorage.getItem('webx11_course_telemetry') !== 'off';
+ renderChapters(); renderAtlas(); renderProgress();
+ }catch(e){ $('reader').innerHTML = '<p style="color:var(--rose)">Course content unreachable: '+esc(e.message)+'</p>'; }
+}
+function renderProgress(){
+ const p = prog(), total = (MANIFEST.chapters||[]).reduce((n,c)=>n+(c.lessons||[]).length,0);
+ $('xp').textContent = (p.xp||0) + ' XP';
+ $('pbar').style.width = total ? Math.round(Object.keys(p.completed).length/total*100)+'%' : '0%';
+}
+function renderAtlas(q){
+ if(!ATLAS){ $('atlas').innerHTML='<p class="small" style="color:var(--rose)">atlas.json missing</p>'; return; }
+ const items = Object.entries(ATLAS.commands).filter(([c,e])=> !q || (c+' '+e.desc+' '+e.module+' '+e.category).toLowerCase().includes(q.toLowerCase()));
+ $('atlas').innerHTML = '<p class="small" style="margin-bottom:6px">'+items.length+' / '+Object.keys(ATLAS.commands).length+' commands</p>'
+ + items.slice(0,60).map(([c,e])=>'<div class="atlas-item"><code>'+esc(c)+'</code><span>'+esc(e.desc||e.module)+' · '+esc(e.tier)+'</span></div>').join('');
+}
+async function renderChapters(){
+ const p = prog(), wrap = $('chapters'); wrap.innerHTML = '';
+ for(const ch of MANIFEST.chapters||[]){
+ const d = document.createElement('div'); d.className='chapter';
+ d.innerHTML = '<span>'+esc(ch.icon||'📘')+' '+esc(ch.title)+'</span><small>'+ (ch.lessons||[]).filter(l=>p.completed[l.id]).length +'/'+(ch.lessons||[]).length+'</small>';
+ wrap.appendChild(d);
+ const box = document.createElement('div'); wrap.appendChild(box);
+ d.onclick = () => { d.classList.toggle('active'); box.style.display = box.style.display==='none'?'block':'none'; };
+ for(const l of ch.lessons||[]){
+ const e = document.createElement('div'); e.className='lesson'+(p.completed[l.id]?' done':''); e.textContent = l.id+' · '+l.title;
+ e.onclick = () => openLesson(ch.id, l.id); box.appendChild(e);
+ }
+ }
+ const p2 = prog(); const all = (MANIFEST.chapters||[]).flatMap(c=>(c.lessons||[]).map(l=>({c,l})));
+ const first = all.find(x=>!p2.completed[x.l.id]) || all[0];
+ if(first) openLesson(first.c.id, first.l.id);
+}
+async function openLesson(chId, lessonId){
+ $('reader').innerHTML = '<p class="small">Loading lesson…</p>';
+ try{
+ const ch = await fetch(BASE+'chapters/'+chId+'.json?v='+V).then(r=>r.json());
+ const l = (ch.lessons||[]).find(x=>x.id===lessonId); if(!l) throw new Error('lesson not found');
+ ACTIVE = {ch, l};
+ const p = prog();
+ let html = '<h2>'+esc(l.title)+'</h2><div class="meta">'+esc(l.objective||'')+' · ⏱ '+(l.minutes||5)+' min · 📘 '+esc(ch.title)+'</div>';
+ for(const b of l.blocks||[]){
+ if(b.type==='cmd') html += '<div class="block cmd"><span>$ '+esc(b.text)+'</span><button class="btn" data-copy="'+esc(b.text)+'">Copy</button></div>';
+ else if(b.type==='code') html += '<pre class="block cmd">'+esc(b.text)+'</pre>';
+ else if(b.type==='tip') html += '<div class="block tip">💡 '+esc(b.text)+'</div>';
+ else if(b.type==='warn') html += '<div class="block warn">⚠️ '+esc(b.text)+'</div>';
+ else if(b.type==='table') html += '<div class="block table">'+esc(b.text)+'</div>';
+ else html += '<p class="block">'+esc(b.text)+'</p>';
+ }
+ if(l.exercise){
+ html += '<div class="exercise"><strong>🏋️ Exercise</strong><p style="margin:6px 0">'+esc(l.exercise.instruction)+'</p>'
+ + '<button class="btn" id="hint-btn">Show hint</button> <button class="btn primary" id="done-btn">Mark done'+(p.completed[l.id]?' ✓':'')+'</button>'
+ + '<p id="hint" style="display:none;margin-top:8px;color:var(--amber)">💡 '+esc(l.exercise.hint||'')+'</p>'
+ + '<p class="small" style="margin-top:8px">Same XP as the terminal: run it in <a href="main.html">main.html</a> with <code>course open '+esc(l.id)+'</code>.</p></div>';
+ }
+ if(l.quiz){
+ html += '<div class="quiz"><strong>❓ '+esc(l.quiz.q)+'</strong>';
+ (l.quiz.options||[]).forEach((o,i)=>{ html += '<button class="opt" data-opt="'+i+'">'+(i+1)+') '+esc(o)+'</button>'; });
+ html += '<p id="quiz-msg" style="margin-top:6px"></p></div>';
+ }
+ $('reader').innerHTML = html;
+ $('reader').querySelectorAll('[data-copy]').forEach(b=>b.onclick=()=>{ navigator.clipboard&&navigator.clipboard.writeText(b.dataset.copy); b.textContent='Copied'; });
+ if($('hint-btn')) $('hint-btn').onclick=()=>{ $('hint').style.display='block'; };
+ if($('done-btn')) $('done-btn').onclick=()=>{ completeLesson(l.id); };
+ $('reader').querySelectorAll('[data-opt]').forEach(b=>b.onclick=()=>{
+ const ok = parseInt(b.dataset.opt,10) === l.quiz.answer;
+ $('quiz-msg').textContent = ok ? '✅ Correct (+5 XP)' : '❌ Not quite — re-read the lesson.';
+ $('quiz-msg').style.color = ok ? 'var(--green)' : 'var(--rose)';
+ if(ok) completeLesson(l.id, 5);
+ });
+ track('lesson_open', {lesson:l.id});
+ }catch(e){ $('reader').innerHTML = '<p style="color:var(--rose)">Failed: '+esc(e.message)+'</p>'; }
+}
+function completeLesson(id, xp){
+ const p = prog(); if(p.completed[id]) return;
+ p.completed[id]=new Date().toISOString(); p.xp=(p.xp||0)+(xp||10); save(p); renderProgress(); renderChapters();
+ track('lesson_complete',{lesson:id});
+}
+function track(type, detail){
+ try{
+ if(localStorage.getItem('webx11_course_telemetry')==='off') return;
+ const sid = localStorage.getItem('webx11_course_sid') || (Math.random().toString(36).slice(2)+Date.now().toString(36));
+ localStorage.setItem('webx11_course_sid', sid);
+ fetch('/spaces/newos4/api/course/event.php',{method:'POST',headers:{'Content-Type':'application/json'},keepalive:true,
+ body:JSON.stringify({sid,type,detail,version:(MANIFEST&&MANIFEST.version)||''})}).catch(()=>{});
+ }catch(e){}
+}
+function setTelemetry(on){ localStorage.setItem('webx11_course_telemetry', on?'on':'off'); }
+function exportProgress(){
+ const blob = new Blob([JSON.stringify(prog(), null, 2)], {type:'application/json'});
+ const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'newos4-course-progress.json'; a.click();
+}
+function tutorContext(){
+ if(!ACTIVE) return 'No lesson selected.';
+ const l = ACTIVE.l;
+ return ['LESSON: '+l.title,'OBJECTIVE: '+l.objective,'COMMANDS: '+(l.commands||[]).join(', ')]
+ .concat((l.blocks||[]).map(b=>b.type.toUpperCase()+': '+b.text))
+ .concat((l.exercise?['EXERCISE: '+l.exercise.instruction+' | HINT: '+l.exercise.hint]:[])).join('\n').slice(0,6000);
+}
+async function askTutor(){
+ const q = $('tutor-q').value.trim(); if(!q) return;
+ const log = $('tutor-log'); log.innerHTML += '<div style="margin:6px 0;color:var(--text)"><b>You:</b> '+esc(q)+'</div>';
+ $('tutor-q').value='';
+ const tok = localStorage.getItem('webx11_session_id') || '';
+ const headers = {'Content-Type':'application/json'};
+ if(tok) headers.Authorization = ['Bearer', tok].join(' ');
+ const sys = 'You are the newOS4 course tutor. Answer in <=120 words using only the lesson context. If not covered, say so and point to `help <cmd>` — never invent commands.\n\n'+tutorContext();
+ try{
+ const r = await fetch('/ai/v1/chat/completions',{method:'POST',headers,
+ body:JSON.stringify({model:'gemma4:31b-cloud',stream:false,messages:[{role:'system',content:sys},{role:'user',content:q}]})});
+ if(!r.ok) throw new Error('HTTP '+r.status+(r.status===401?' — log in first':''));
+ const d = await r.json();
+ const a = (d.choices&&d.choices[0]&&d.choices[0].message&&d.choices[0].message.content)||'(empty)';
+ log.innerHTML += '<div style="margin:6px 0;color:var(--cyan)"><b>Tutor:</b> '+esc(a)+'</div>';
+ track('question',{q:q.slice(0,240),lesson:ACTIVE?ACTIVE.l.id:null});
+ }catch(e){ log.innerHTML += '<div style="margin:6px 0;color:var(--rose)">'+esc(e.message)+'</div>'; }
+ log.scrollTop = log.scrollHeight;
+}
+async function showProjects(){
+ $('reader').innerHTML = '<h2>🛠 Example projects</h2>';
+ for(const id of MANIFEST.projects||[]){
+ const pr = await fetch(BASE+'projects/'+id+'.json?v='+V).then(r=>r.json()).catch(()=>null); if(!pr) continue;
+ const div = document.createElement('div'); div.className='exercise'; div.style.marginTop='12px';
+ div.innerHTML = '<strong>'+esc(pr.title)+'</strong> <span class="chip">'+esc(pr.level||'beginner')+'</span> <span class="chip">'+pr.minutes+' min</span>'
+ + '<p class="small" style="margin:6px 0">'+esc(pr.goal)+'</p>'
+ + (pr.steps||[]).map((s,i)=>'<div class="block" style="margin:4px 0">'+(i+1)+'. '+esc(s.instruction)+(s.command?' <code style="color:var(--green)">'+esc(s.command)+'</code>':'')+'</div>').join('');
+ $('reader').appendChild(div);
+ track('project_view',{project:id});
+ }
+}
+async function showFeed(){
+ $('reader').innerHTML = '<h2>📰 Daily lessons</h2><p class="small">AI-expanded micro-lessons, newest first.</p>';
+ const f = await fetch(BASE+'feed.json?v='+V).then(r=>r.json()).catch(()=>({items:[]}));
+ if(!(f.items||[]).length) $('reader').innerHTML += '<p class="small" style="margin-top:10px">No daily lessons yet — the generator adds one at the next 05:15 run.</p>';
+ for(const it of f.items||[]){
+ const d = document.createElement('div'); d.className='exercise'; d.style.marginTop='12px';
+ d.innerHTML = '<strong>'+esc(it.title)+'</strong> <span class="chip">'+esc(it.date)+'</span>'
+ + (it.blocks||[]).map(b=> b.type==='cmd' ? '<div class="block cmd">$ '+esc(b.text)+'</div>' : '<p class="block">'+esc(b.text)+'</p>').join('')
+ + (it.sources&&it.sources.length ? '<p class="small">Sources: '+it.sources.map(s=>'<a href="'+esc(s)+'" target="_blank" rel="noopener">'+esc(s.replace(/^https?:\/\//,'').slice(0,40))+'</a>').join(' · ')+'</p>' : '');
+ $('reader').appendChild(d);
+ }
+}
+$('atlas-q').addEventListener('input', e => renderAtlas(e.target.value.trim()));
+$('tutor-send').onclick = askTutor;
+$('tutor-q').addEventListener('keydown', e => { if(e.key==='Enter') askTutor(); });
+boot();
+</script>
+</body>
+</html>
diff --git a/course/atlas.json b/course/atlas.json
new file mode 100644
index 0000000..e1c6a4e
--- /dev/null
+++ b/course/atlas.json
@@ -0,0 +1,3768 @@
+{
+ "version": "2026-10-18T12:01:30Z",
+ "source_hash": "3295c562f4c4dd30",
+ "commands": {
+ "a2a-ack": {
+ "module": "module-agent-registry",
+ "file": "modules/core/module-agent-registry.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "a2a-ack <msgId> <result...> — Acknowledge a message with result",
+ "builtin": false,
+ "example": "a2a-ack"
+ },
+ "a2a-list": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "a2a-list — Show pending A2A messages across all agents",
+ "builtin": false,
+ "example": "a2a-list"
+ },
+ "a2a-poll": {
+ "module": "module-agent-registry",
+ "file": "modules/core/module-agent-registry.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "a2a-poll [agentId] — Poll for messages addressed to an agent (default: current or \"user\")",
+ "builtin": false,
+ "example": "a2a-poll"
+ },
+ "a2a-post": {
+ "module": "module-agent-registry",
+ "file": "modules/core/module-agent-registry.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "a2a-post <to> <type> <capability> <payload...> — Post an A2A message",
+ "builtin": false,
+ "example": "a2a-post"
+ },
+ "a2a-prune": {
+ "module": "module-agent-registry",
+ "file": "modules/core/module-agent-registry.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "a2a-prune — Clean up expired and old completed messages",
+ "builtin": false,
+ "example": "a2a-prune"
+ },
+ "a2a-reject": {
+ "module": "module-agent-registry",
+ "file": "modules/core/module-agent-registry.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "a2a-reject <msgId> <error...> — Reject a message with error",
+ "builtin": false,
+ "example": "a2a-reject"
+ },
+ "a2a-send": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "a2a-send <agent-id> <json-message> — Send an A2A message to an agent's inbox",
+ "builtin": false,
+ "example": "a2a-send"
+ },
+ "a2a-status": {
+ "module": "module-agent-registry",
+ "file": "modules/core/module-agent-registry.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "a2a-status — Show scratchpad stats per agent",
+ "builtin": false,
+ "example": "a2a-status"
+ },
+ "agent-list": {
+ "module": "module-agent-registry",
+ "file": "modules/core/module-agent-registry.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "module-agent-registry.js — VFS-backed agent directory with A2A contracts",
+ "builtin": false,
+ "example": "agent-list"
+ },
+ "agent-loops": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "agent-loops [name|new|edit|delete|clone|export|reset] — Manage agent loop profiles",
+ "builtin": false,
+ "example": "agent-loops"
+ },
+ "agent-register": {
+ "module": "module-agent-registry",
+ "file": "modules/core/module-agent-registry.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "module-agent-registry.js — VFS-backed agent directory with A2A contracts",
+ "builtin": false,
+ "example": "agent-register"
+ },
+ "agent-status": {
+ "module": "module-agent-registry",
+ "file": "modules/core/module-agent-registry.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "module-agent-registry.js — VFS-backed agent directory with A2A contracts",
+ "builtin": false,
+ "example": "agent-status"
+ },
+ "agent-unregister": {
+ "module": "module-agent-registry",
+ "file": "modules/core/module-agent-registry.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "module-agent-registry.js — VFS-backed agent directory with A2A contracts",
+ "builtin": false,
+ "example": "agent-unregister"
+ },
+ "ai": {
+ "module": "module-ollama",
+ "file": "modules/core/module-ollama.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "Extracted from newOS4 index.html — WebX11AIAssistantModule (Full Agent Loop)",
+ "builtin": false,
+ "example": "ai"
+ },
+ "ai-allow": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ai-allow — Confirm pending AI action",
+ "builtin": false,
+ "example": "ai-allow"
+ },
+ "ai-clear": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ai-clear — Clear conversation context",
+ "builtin": false,
+ "example": "ai-clear"
+ },
+ "ai-compress": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ai-compress — command from module-aiassist",
+ "builtin": false,
+ "example": "ai-compress"
+ },
+ "ai-config": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ai-config [key] [value] — View or set AI loop config (max-turns, max-context, confirm-timeout(0=disabled))",
+ "builtin": false,
+ "example": "ai-config"
+ },
+ "ai-context": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ai-context — Show current conversation context",
+ "builtin": false,
+ "example": "ai-context"
+ },
+ "ai-deny": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ai-deny — Deny pending AI action",
+ "builtin": false,
+ "example": "ai-deny"
+ },
+ "ai-ignore": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ai-ignore — Suppress validator warnings for this session",
+ "builtin": false,
+ "example": "ai-ignore"
+ },
+ "ai-model": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ai-model [model] — Show or set AI model",
+ "builtin": false,
+ "example": "ai-model"
+ },
+ "ai-stop": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ai-stop — Cancel current AI operation",
+ "builtin": false,
+ "example": "ai-stop"
+ },
+ "ai-system": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ai-system <prompt> — Set system prompt",
+ "builtin": false,
+ "example": "ai-system"
+ },
+ "alias": {
+ "module": "alias",
+ "file": "modules/ext/alias.js",
+ "tier": "critical-ext-bundle",
+ "category": "general",
+ "desc": "List, set, or show aliases (usage: alias [name=value])",
+ "builtin": false,
+ "example": "alias"
+ },
+ "analyze": {
+ "module": "media",
+ "file": "modules/ext/media.js",
+ "tier": "ext-auto",
+ "category": "general",
+ "desc": "analyze <file> — Analyze image/audio/video. Flags: --frames N, --spectrogram, --beats, --scene",
+ "builtin": false,
+ "example": "analyze"
+ },
+ "archive-list": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "archive-list — command from module-aiassist",
+ "builtin": false,
+ "example": "archive-list"
+ },
+ "archive-read": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "archive-read — command from module-aiassist",
+ "builtin": false,
+ "example": "archive-read"
+ },
+ "ask": {
+ "module": "module-ollama",
+ "file": "modules/core/module-ollama.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "Extracted from newOS4 index.html — WebX11AIAssistantModule (Full Agent Loop)",
+ "builtin": false,
+ "example": "ask"
+ },
+ "base64": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "base64 [-d] <string> — Base64 encode/decode",
+ "builtin": false,
+ "example": "base64"
+ },
+ "basename": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "basename <path> — Strip directory from path",
+ "builtin": false,
+ "example": "basename"
+ },
+ "bench": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "bench [profile] [--tasks=ids] [--repeat=N] — Run benchmark suite on a profile",
+ "builtin": false,
+ "example": "bench"
+ },
+ "bench-compare": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "bench-compare <p1> <p2> — Compare benchmark results of two profiles",
+ "builtin": false,
+ "example": "bench-compare"
+ },
+ "bug-report": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "bug-report — Generate a markdown bug report to /tmp/ for debugging",
+ "builtin": false,
+ "example": "bug-report"
+ },
+ "cal": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "cal [month] [year] — Display an ASCII calendar",
+ "builtin": false,
+ "example": "cal"
+ },
+ "calc": {
+ "module": "calc",
+ "file": "modules/ext/calc.js",
+ "tier": "ext-auto",
+ "category": "general",
+ "desc": "calc <expression> or calc <value> <from-unit> to <to-unit> — Evaluate expressions or convert between units",
+ "builtin": false,
+ "example": "calc"
+ },
+ "cat": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "cat <file> — Display file contents",
+ "builtin": false,
+ "example": "cat"
+ },
+ "cd": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "cd <path> — Change current directory; cd / to go to root",
+ "builtin": false,
+ "example": "cd"
+ },
+ "chat": {
+ "module": "module-chatstudio",
+ "file": "modules/core/module-chatstudio.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "Toggle the ChatStudio overlay (inside: /setapi, /setmodel, /ask, /help)",
+ "builtin": false,
+ "example": "chat"
+ },
+ "checkpoint": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "checkpoint [name] — Create a VFS snapshot (name defaults to ISO timestamp)",
+ "builtin": false,
+ "example": "checkpoint"
+ },
+ "checkpoint-del": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "checkpoint-del <name> — Delete a VFS checkpoint",
+ "builtin": false,
+ "example": "checkpoint-del"
+ },
+ "checkpoint-list": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "checkpoint-list — List all VFS checkpoints",
+ "builtin": false,
+ "example": "checkpoint-list"
+ },
+ "clear": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "Extracted from newOS4 index.html — WebX11Terminal",
+ "builtin": true,
+ "example": "clear"
+ },
+ "cmd-allow": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "Extracted from newOS4 index.html — WebX11Terminal",
+ "builtin": true,
+ "example": "cmd-allow"
+ },
+ "cmd-deny": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "Extracted from newOS4 index.html — WebX11Terminal",
+ "builtin": true,
+ "example": "cmd-deny"
+ },
+ "commit-approve": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "commit-approve — command from module-aiassist",
+ "builtin": false,
+ "example": "commit-approve"
+ },
+ "commit-deny": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "commit-deny — command from module-aiassist",
+ "builtin": false,
+ "example": "commit-deny"
+ },
+ "commit-status": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "commit-status — command from module-aiassist",
+ "builtin": false,
+ "example": "commit-status"
+ },
+ "config": {
+ "module": "module-config",
+ "file": "modules/core/module-config.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "config [key] — Show all config or a specific key",
+ "builtin": false,
+ "example": "config"
+ },
+ "config-delete": {
+ "module": "module-config",
+ "file": "modules/core/module-config.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "config-delete <key> — Delete a configuration key",
+ "builtin": false,
+ "example": "config-delete"
+ },
+ "config-export": {
+ "module": "module-config",
+ "file": "modules/core/module-config.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "config-export — Export all config as JSON",
+ "builtin": false,
+ "example": "config-export"
+ },
+ "config-list": {
+ "module": "module-config",
+ "file": "modules/core/module-config.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "config-list — List all configuration keys",
+ "builtin": false,
+ "example": "config-list"
+ },
+ "config-reset": {
+ "module": "module-config",
+ "file": "modules/core/module-config.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "config-reset — Reset all configuration to defaults",
+ "builtin": false,
+ "example": "config-reset"
+ },
+ "config-set": {
+ "module": "module-config",
+ "file": "modules/core/module-config.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "config-set <key> <value> — Set a configuration value",
+ "builtin": false,
+ "example": "config-set"
+ },
+ "consensus": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "consensus <question> — Start a consensus round: spawn 3 agents, debate the question, resolve by majority",
+ "builtin": false,
+ "example": "consensus"
+ },
+ "consensus-history": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "consensus-history — Show past consensus rounds and their resolutions",
+ "builtin": false,
+ "example": "consensus-history"
+ },
+ "consensus-status": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "consensus-status — Show current/active consensus round status",
+ "builtin": false,
+ "example": "consensus-status"
+ },
+ "context": {
+ "module": "module-smartctx",
+ "file": "modules/core/module-smartctx.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "context [name] — Set or view current context profile",
+ "builtin": false,
+ "example": "context"
+ },
+ "context-auto": {
+ "module": "module-smartctx",
+ "file": "modules/core/module-smartctx.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "context-auto [on|off] — Toggle auto-routing (detects intent from user input)",
+ "builtin": false,
+ "example": "context-auto"
+ },
+ "context-delete": {
+ "module": "module-smartctx",
+ "file": "modules/core/module-smartctx.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "context-delete <name> — Delete a context profile",
+ "builtin": false,
+ "example": "context-delete"
+ },
+ "context-new": {
+ "module": "module-smartctx",
+ "file": "modules/core/module-smartctx.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "context-new <name> <displayName> <systemPrompt> — Create a new context profile",
+ "builtin": false,
+ "example": "context-new"
+ },
+ "contexts": {
+ "module": "module-smartctx",
+ "file": "modules/core/module-smartctx.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "contexts — List all available context profiles",
+ "builtin": false,
+ "example": "contexts"
+ },
+ "contract-list": {
+ "module": "module-agent-registry",
+ "file": "modules/core/module-agent-registry.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "module-agent-registry.js — VFS-backed agent directory with A2A contracts",
+ "builtin": false,
+ "example": "contract-list"
+ },
+ "contract-register": {
+ "module": "module-agent-registry",
+ "file": "modules/core/module-agent-registry.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "module-agent-registry.js — VFS-backed agent directory with A2A contracts",
+ "builtin": false,
+ "example": "contract-register"
+ },
+ "cp": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "cp <source> <dest> — Copy file or directory",
+ "builtin": false,
+ "example": "cp"
+ },
+ "cron-clear": {
+ "module": "cron",
+ "file": "modules/ext/cron.js",
+ "tier": "ext-auto",
+ "category": "automation",
+ "desc": "cron-clear — Stop all cron jobs",
+ "builtin": false,
+ "example": "cron-clear"
+ },
+ "cron-every": {
+ "module": "cron",
+ "file": "modules/ext/cron.js",
+ "tier": "ext-auto",
+ "category": "automation",
+ "desc": "cron-every <seconds> <command> — Schedule a command to run every N seconds",
+ "builtin": false,
+ "example": "cron-every"
+ },
+ "cron-list": {
+ "module": "cron",
+ "file": "modules/ext/cron.js",
+ "tier": "ext-auto",
+ "category": "automation",
+ "desc": "cron-list — List all active cron jobs",
+ "builtin": false,
+ "example": "cron-list"
+ },
+ "cron-stop": {
+ "module": "cron",
+ "file": "modules/ext/cron.js",
+ "tier": "ext-auto",
+ "category": "automation",
+ "desc": "cron-stop <id> — Stop a specific cron job",
+ "builtin": false,
+ "example": "cron-stop"
+ },
+ "cut": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "cut -f<N> [-d<delim>] <file> — Extract column from file",
+ "builtin": false,
+ "example": "cut"
+ },
+ "dashboard": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "dashboard — command from terminal",
+ "builtin": true,
+ "example": "dashboard"
+ },
+ "date": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "date — Show current date/time (ISO, local, UTC, timestamp)",
+ "builtin": false,
+ "example": "date"
+ },
+ "debug": {
+ "module": "debug-panel",
+ "file": "modules/ext/debug-panel.js",
+ "tier": "ext-auto",
+ "category": "dev",
+ "desc": "debug — Toggle the debug console panel",
+ "builtin": false,
+ "example": "debug"
+ },
+ "debug-ai": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "debug-ai — Toggle AI tool-call debug panel",
+ "builtin": false,
+ "example": "debug-ai"
+ },
+ "debug-ai-last": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "debug-ai-last — command from module-aiassist",
+ "builtin": false,
+ "example": "debug-ai-last"
+ },
+ "debug-ai-log": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "debug-ai-log — command from module-aiassist",
+ "builtin": false,
+ "example": "debug-ai-log"
+ },
+ "debug-ai-send": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "debug-ai-send — command from module-aiassist",
+ "builtin": false,
+ "example": "debug-ai-send"
+ },
+ "debug-analyze": {
+ "module": "module-debug-supervisor",
+ "file": "modules/core/module-debug-supervisor.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "debug-analyze <hash> — Deep analysis of a specific error",
+ "builtin": false,
+ "example": "debug-analyze"
+ },
+ "debug-fix": {
+ "module": "module-debug-supervisor",
+ "file": "modules/core/module-debug-supervisor.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "debug-fix <hash> — Suggest and optionally apply fix for an error",
+ "builtin": false,
+ "example": "debug-fix"
+ },
+ "debug-health": {
+ "module": "module-debug-supervisor",
+ "file": "modules/core/module-debug-supervisor.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "debug-health — Check supervisor module health status",
+ "builtin": false,
+ "example": "debug-health"
+ },
+ "debug-history": {
+ "module": "module-debug-supervisor",
+ "file": "modules/core/module-debug-supervisor.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "debug-history [n] — Show last N errors (default: 20)",
+ "builtin": false,
+ "example": "debug-history"
+ },
+ "debug-stats": {
+ "module": "module-debug-supervisor",
+ "file": "modules/core/module-debug-supervisor.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "debug-stats — Show error summary (total, unique, clusters, top issues)",
+ "builtin": false,
+ "example": "debug-stats"
+ },
+ "debug-sync": {
+ "module": "module-debug-supervisor",
+ "file": "modules/core/module-debug-supervisor.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "debug-sync [url] — Sync errors to backend API (or set sync URL)",
+ "builtin": false,
+ "example": "debug-sync"
+ },
+ "debug-sync-url": {
+ "module": "module-debug-supervisor",
+ "file": "modules/core/module-debug-supervisor.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "debug-sync-url [url] — View or set the backend sync endpoint",
+ "builtin": false,
+ "example": "debug-sync-url"
+ },
+ "debug-trends": {
+ "module": "module-debug-supervisor",
+ "file": "modules/core/module-debug-supervisor.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "debug-trends [days] — Error frequency over time (default: 7)",
+ "builtin": false,
+ "example": "debug-trends"
+ },
+ "decrypt": {
+ "module": "cryptogzip",
+ "file": "modules/ext/cryptogzip.js",
+ "tier": "ext-auto",
+ "category": "system",
+ "desc": "decrypt <path.enc> <passphrase> — Decrypt a .enc file",
+ "builtin": false,
+ "example": "decrypt"
+ },
+ "diff": {
+ "module": "diff",
+ "file": "modules/ext/diff.js",
+ "tier": "critical-ext-bundle",
+ "category": "editor",
+ "desc": "Show differences between two VFS files or compare module source (usage: diff <file1> <file2> or diff --module <name>)",
+ "builtin": false,
+ "example": "diff"
+ },
+ "dirname": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "dirname <path> — Strip filename from path",
+ "builtin": false,
+ "example": "dirname"
+ },
+ "doctor": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "doctor — Run all health checks, rank issues, show top 3 warnings",
+ "builtin": false,
+ "example": "doctor"
+ },
+ "download": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "download <file> — Download a file to your device",
+ "builtin": false,
+ "example": "download"
+ },
+ "du": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "du [path] — Estimate file/directory size",
+ "builtin": false,
+ "example": "du"
+ },
+ "echo": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "echo <text...> — Print text to the terminal",
+ "builtin": false,
+ "example": "echo"
+ },
+ "edit": {
+ "module": "editor",
+ "file": "modules/ext/editor.js",
+ "tier": "critical-ext-bundle",
+ "category": "editor",
+ "desc": "Open the code editor for a VFS file (usage: edit <path>)",
+ "builtin": false,
+ "example": "edit"
+ },
+ "empty-trash": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "empty-trash — Permanently delete all trash items",
+ "builtin": false,
+ "example": "empty-trash"
+ },
+ "encrypt": {
+ "module": "cryptogzip",
+ "file": "modules/ext/cryptogzip.js",
+ "tier": "ext-auto",
+ "category": "system",
+ "desc": "encrypt <path> <passphrase> — AES-256-GCM encrypt a file",
+ "builtin": false,
+ "example": "encrypt"
+ },
+ "env": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "env — List environment variables (webx11_ keys + user profile)",
+ "builtin": false,
+ "example": "env"
+ },
+ "errors": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "errors [n] — Show last n ERROR log entries (default: 20)",
+ "builtin": false,
+ "example": "errors"
+ },
+ "expand": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "expand <file> — Convert tabs to spaces (default 4) in file",
+ "builtin": false,
+ "example": "expand"
+ },
+ "export": {
+ "module": "shell",
+ "file": "modules/ext/shell.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "export <var>=<value> — Set a shell variable",
+ "builtin": false,
+ "example": "export"
+ },
+ "extract": {
+ "module": "module-fetcher",
+ "file": "modules/core/module-fetcher.js",
+ "tier": "core-bundle",
+ "category": "web",
+ "desc": "extract <url> — Extract readable article content (title + body text) from a URL",
+ "builtin": false,
+ "example": "extract"
+ },
+ "factory-reset": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "factory-reset — Alias for reset all",
+ "builtin": false,
+ "example": "factory-reset"
+ },
+ "factory-reset-old": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "factory-reset-old — command from module-system",
+ "builtin": false,
+ "example": "factory-reset-old"
+ },
+ "fetch": {
+ "module": "module-fetcher",
+ "file": "modules/core/module-fetcher.js",
+ "tier": "core-bundle",
+ "category": "web",
+ "desc": "fetch <url> [--post=BODY] [--type=CT] [--raw] — Fetch URL (raw HTML/text); --post enables POST with body, --type sets Content-Type, --raw shows untruncated content",
+ "builtin": false,
+ "example": "fetch"
+ },
+ "files": {
+ "module": "filemgr",
+ "file": "modules/ext/filemgr.js",
+ "tier": "critical-ext-bundle",
+ "category": "filesystem",
+ "desc": "files — Open the file manager overlay with VFS directory listing, preview, encrypt, zip",
+ "builtin": false,
+ "example": "files"
+ },
+ "find": {
+ "module": "finder",
+ "file": "modules/ext/finder.js",
+ "tier": "critical-ext-bundle",
+ "category": "filesystem",
+ "desc": "Recursively search VFS files/directories by name (usage: find <query>)",
+ "builtin": false,
+ "example": "find"
+ },
+ "floating-add": {
+ "module": "floating",
+ "file": "modules/ext/floating.js",
+ "tier": "ext-auto",
+ "category": "ui",
+ "desc": "floating-add btn|menu|panel <id> <label> [cmd|btn:Label:cmd...] — Add a floating UI element",
+ "builtin": false,
+ "example": "floating-add"
+ },
+ "floating-clear": {
+ "module": "floating",
+ "file": "modules/ext/floating.js",
+ "tier": "ext-auto",
+ "category": "ui",
+ "desc": "floating-clear — Remove all floating elements",
+ "builtin": false,
+ "example": "floating-clear"
+ },
+ "floating-list": {
+ "module": "floating",
+ "file": "modules/ext/floating.js",
+ "tier": "ext-auto",
+ "category": "ui",
+ "desc": "floating-list — Show all floating elements",
+ "builtin": false,
+ "example": "floating-list"
+ },
+ "floating-pos": {
+ "module": "floating",
+ "file": "modules/ext/floating.js",
+ "tier": "ext-auto",
+ "category": "ui",
+ "desc": "floating-pos <id> <x|auto> <y|auto> — Reposition a floating element",
+ "builtin": false,
+ "example": "floating-pos"
+ },
+ "floating-remove": {
+ "module": "floating",
+ "file": "modules/ext/floating.js",
+ "tier": "ext-auto",
+ "category": "ui",
+ "desc": "floating-remove <id> — Remove a floating element",
+ "builtin": false,
+ "example": "floating-remove"
+ },
+ "floating-toggle": {
+ "module": "floating",
+ "file": "modules/ext/floating.js",
+ "tier": "ext-auto",
+ "category": "ui",
+ "desc": "floating-toggle <id> — Show/hide a floating element",
+ "builtin": false,
+ "example": "floating-toggle"
+ },
+ "font-size": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "font-size <8-32> — Set terminal font size (persists across sessions)",
+ "builtin": false,
+ "example": "font-size"
+ },
+ "grep": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "grep <pattern> [file] — Search file content for pattern",
+ "builtin": false,
+ "example": "grep"
+ },
+ "guardrail": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "guardrail — command from module-aiassist",
+ "builtin": false,
+ "example": "guardrail"
+ },
+ "guardrail-add": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "guardrail-add — command from module-aiassist",
+ "builtin": false,
+ "example": "guardrail-add"
+ },
+ "guardrail-remove": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "guardrail-remove — command from module-aiassist",
+ "builtin": false,
+ "example": "guardrail-remove"
+ },
+ "gui": {
+ "module": "module-simplegui",
+ "file": "modules/core/module-simplegui.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "gui — Show the GUI builder overview",
+ "builtin": false,
+ "example": "gui"
+ },
+ "gui-button": {
+ "module": "module-simplegui",
+ "file": "modules/core/module-simplegui.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "gui-button <label> <command> — Add a clickable button",
+ "builtin": false,
+ "example": "gui-button"
+ },
+ "gui-clear": {
+ "module": "module-simplegui",
+ "file": "modules/core/module-simplegui.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "gui-clear — Clear all GUI elements",
+ "builtin": false,
+ "example": "gui-clear"
+ },
+ "gui-input": {
+ "module": "module-simplegui",
+ "file": "modules/core/module-simplegui.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "gui-input <placeholder> <varName> — Add a text input field",
+ "builtin": false,
+ "example": "gui-input"
+ },
+ "gui-run": {
+ "module": "module-simplegui",
+ "file": "modules/core/module-simplegui.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "gui-run <command> — Run a terminal command from the GUI",
+ "builtin": false,
+ "example": "gui-run"
+ },
+ "gui-text": {
+ "module": "module-simplegui",
+ "file": "modules/core/module-simplegui.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "gui-text <content> — Add a text element",
+ "builtin": false,
+ "example": "gui-text"
+ },
+ "gunzip": {
+ "module": "cryptogzip",
+ "file": "modules/ext/cryptogzip.js",
+ "tier": "ext-auto",
+ "category": "system",
+ "desc": "gunzip <path.gz> — Decompress a .gz file (binary-safe)",
+ "builtin": false,
+ "example": "gunzip"
+ },
+ "gzip": {
+ "module": "cryptogzip",
+ "file": "modules/ext/cryptogzip.js",
+ "tier": "ext-auto",
+ "category": "system",
+ "desc": "gzip <path> — Compress a VFS file to .gz",
+ "builtin": false,
+ "example": "gzip"
+ },
+ "head": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "head <file> [lines] — Show first N lines (default: 10)",
+ "builtin": false,
+ "example": "head"
+ },
+ "health-check": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "health-check — Audit VFS size, modules, logs, trash, skills, tool failures",
+ "builtin": false,
+ "example": "health-check"
+ },
+ "help": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "help [command] — Show all commands or usage for a specific command",
+ "builtin": false,
+ "example": "help"
+ },
+ "history": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "history — command from terminal",
+ "builtin": true,
+ "example": "history"
+ },
+ "hostname": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "hostname — Show host/user-agent info",
+ "builtin": false,
+ "example": "hostname"
+ },
+ "images": {
+ "module": "module-searxng",
+ "file": "modules/core/module-searxng.js",
+ "tier": "core-bundle",
+ "category": "web",
+ "desc": "images <query> — Search for images",
+ "builtin": false,
+ "example": "images"
+ },
+ "js": {
+ "module": "console",
+ "file": "modules/ext/console.js",
+ "tier": "ext-auto",
+ "category": "dev",
+ "desc": "js <expression> — Evaluate JavaScript in page context",
+ "builtin": false,
+ "example": "js"
+ },
+ "js-run": {
+ "module": "console",
+ "file": "modules/ext/console.js",
+ "tier": "ext-auto",
+ "category": "dev",
+ "desc": "js-run <path> — Load and execute a JS file from VFS",
+ "builtin": false,
+ "example": "js-run"
+ },
+ "kanban": {
+ "module": "kanban",
+ "file": "modules/ext/kanban.js",
+ "tier": "ext-auto",
+ "category": "automation",
+ "desc": "kanban <command> [args] — Manage tasks on a kanban board (todo, doing, done columns)",
+ "builtin": false,
+ "example": "kanban"
+ },
+ "learn-skill": {
+ "module": "skill-pack-core",
+ "file": "modules/ext/skill-pack-core.js",
+ "tier": "ext-auto",
+ "category": "ai",
+ "desc": "learn-skill <topic> — Reflect on topic, draft a skill from experience",
+ "builtin": false,
+ "example": "learn-skill"
+ },
+ "lease-create": {
+ "module": "module-leases",
+ "file": "modules/core/module-leases.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "module-leases.js — Lease system for agent task delegation",
+ "builtin": false,
+ "example": "lease-create"
+ },
+ "lease-list": {
+ "module": "module-leases",
+ "file": "modules/core/module-leases.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "module-leases.js — Lease system for agent task delegation",
+ "builtin": false,
+ "example": "lease-list"
+ },
+ "lease-renew": {
+ "module": "module-leases",
+ "file": "modules/core/module-leases.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "lease-renew — command from module-leases",
+ "builtin": false,
+ "example": "lease-renew"
+ },
+ "lease-revoke": {
+ "module": "module-leases",
+ "file": "modules/core/module-leases.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "module-leases.js — Lease system for agent task delegation",
+ "builtin": false,
+ "example": "lease-revoke"
+ },
+ "listmods": {
+ "module": "module-modmgr",
+ "file": "modules/core/module-modmgr.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "listmods — List all loaded modules with status",
+ "builtin": false,
+ "example": "listmods"
+ },
+ "load-local": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "Extracted from newOS4 index.html — ModuleManager",
+ "builtin": true,
+ "example": "load-local"
+ },
+ "loadlocal": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "Extracted from newOS4 index.html — ModuleManager",
+ "builtin": true,
+ "example": "loadlocal"
+ },
+ "loadmod": {
+ "module": "module-modmgr",
+ "file": "modules/core/module-modmgr.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "loadmod <url> — Load a module from a URL",
+ "builtin": false,
+ "example": "loadmod"
+ },
+ "logrotate": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "logrotate — Rotate current.log to a dated archive (auto-cleans old)",
+ "builtin": false,
+ "example": "logrotate"
+ },
+ "logs": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "logs [n] [LEVEL] — Show last n log entries (optionally filtered by level: INFO/WARN/ERROR)",
+ "builtin": false,
+ "example": "logs"
+ },
+ "ls": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "ls [path] [-la] — List files and directories",
+ "builtin": false,
+ "example": "ls"
+ },
+ "ls-trash": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "ls-trash — List items in trash",
+ "builtin": false,
+ "example": "ls-trash"
+ },
+ "mem": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "mem — Show memory usage (heap + localStorage + VFS)",
+ "builtin": false,
+ "example": "mem"
+ },
+ "mem-debug": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "mem-debug — Show memory breakdown by component",
+ "builtin": false,
+ "example": "mem-debug"
+ },
+ "memgraph-agent": {
+ "module": "module-memory-graph",
+ "file": "modules/core/module-memory-graph.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "memgraph-agent <agentId> — Show namespaced nodes for an agent",
+ "builtin": false,
+ "example": "memgraph-agent"
+ },
+ "memgraph-decay-config": {
+ "module": "module-memory-graph",
+ "file": "modules/core/module-memory-graph.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "memgraph-decay-config <days> <min_uses> — Configure decay policy (default: 30d, 3 uses)",
+ "builtin": false,
+ "example": "memgraph-decay-config"
+ },
+ "memgraph-export": {
+ "module": "module-memory-graph",
+ "file": "modules/core/module-memory-graph.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "memgraph-export — Export graph as JSON",
+ "builtin": false,
+ "example": "memgraph-export"
+ },
+ "memgraph-learn": {
+ "module": "module-memory-graph",
+ "file": "modules/core/module-memory-graph.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "memgraph-learn <entity> <fact> — Add a fact to the memory graph",
+ "builtin": false,
+ "example": "memgraph-learn"
+ },
+ "memgraph-prune": {
+ "module": "module-memory-graph",
+ "file": "modules/core/module-memory-graph.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "memgraph-prune — Remove stale low-weight edges (>30d, <3 accesses)",
+ "builtin": false,
+ "example": "memgraph-prune"
+ },
+ "memgraph-search": {
+ "module": "module-memory-graph",
+ "file": "modules/core/module-memory-graph.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "memgraph-search <query> — Find nodes by label or type",
+ "builtin": false,
+ "example": "memgraph-search"
+ },
+ "memgraph-session": {
+ "module": "module-memory-graph",
+ "file": "modules/core/module-memory-graph.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "memgraph-session [date] — Show graph nodes for a session (default: today)",
+ "builtin": false,
+ "example": "memgraph-session"
+ },
+ "memgraph-stats": {
+ "module": "module-memory-graph",
+ "file": "modules/core/module-memory-graph.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "memgraph-stats — Most connected nodes, strongest edges, cluster info",
+ "builtin": false,
+ "example": "memgraph-stats"
+ },
+ "memgraph-status": {
+ "module": "module-memory-graph",
+ "file": "modules/core/module-memory-graph.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "memgraph-status — Show node/edge counts, last access",
+ "builtin": false,
+ "example": "memgraph-status"
+ },
+ "memory-map-export": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "memory-map-export — Export memory index to /memory/reference.md (overwrites file)",
+ "builtin": false,
+ "example": "memory-map-export"
+ },
+ "mindmap": {
+ "module": "mindmap",
+ "file": "modules/ext/mindmap.js",
+ "tier": "ext-auto",
+ "category": "general",
+ "desc": "mindmap <subcommand> [...] — Create, edit, visualize mindmaps. v4.5.44: multi-mindmap, find, move, undo, import, merge, diff, stats, mermaid, refresh-html. Run `mindmap help` for full list.",
+ "builtin": false,
+ "example": "mindmap"
+ },
+ "mkdir": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "mkdir <dir> [-p] — Create a directory; -p creates parent paths",
+ "builtin": false,
+ "example": "mkdir"
+ },
+ "model-delete": {
+ "module": "module-ollama",
+ "file": "modules/core/module-ollama.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "model-delete <model-name> — Delete a model from local storage",
+ "builtin": false,
+ "example": "model-delete"
+ },
+ "model-info": {
+ "module": "module-ollama",
+ "file": "modules/core/module-ollama.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "model-info <model-name> — Show details about a specific model",
+ "builtin": false,
+ "example": "model-info"
+ },
+ "model-pull": {
+ "module": "module-ollama",
+ "file": "modules/core/module-ollama.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "model-pull <model-name> — Pull a model from Ollama registry",
+ "builtin": false,
+ "example": "model-pull"
+ },
+ "model-run": {
+ "module": "module-ollama",
+ "file": "modules/core/module-ollama.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "model-run <model-name> [prompt] — Run inference with a model",
+ "builtin": false,
+ "example": "model-run"
+ },
+ "models": {
+ "module": "module-ollama",
+ "file": "modules/core/module-ollama.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "models — List all available Ollama models (table format)",
+ "builtin": false,
+ "example": "models"
+ },
+ "module-disable": {
+ "module": "module-modmgr",
+ "file": "modules/core/module-modmgr.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "module-disable <name> — Disable a module from auto-loading on refresh (persists)",
+ "builtin": false,
+ "example": "module-disable"
+ },
+ "module-enable": {
+ "module": "module-modmgr",
+ "file": "modules/core/module-modmgr.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "module-enable <name> — Re-enable a previously disabled module",
+ "builtin": false,
+ "example": "module-enable"
+ },
+ "module-info": {
+ "module": "module-modmgr",
+ "file": "modules/core/module-modmgr.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "module-info <name> — Show module details: path, status, size, type, source, commands list",
+ "builtin": false,
+ "example": "module-info"
+ },
+ "module-list": {
+ "module": "module-modmgr",
+ "file": "modules/core/module-modmgr.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "module-list [flags] — List loaded modules grouped by category. Flags: --flat, --core, --ext, --foundation, --lazy, --eager, --loaded, --unloaded, --search=<pat>, --failed, --disabled",
+ "builtin": false,
+ "example": "module-list"
+ },
+ "module-load": {
+ "module": "module-modmgr",
+ "file": "modules/core/module-modmgr.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "module-load <name> — Mark a lazy module as loaded",
+ "builtin": false,
+ "example": "module-load"
+ },
+ "module-reload": {
+ "module": "module-modmgr",
+ "file": "modules/core/module-modmgr.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "module-reload <name> — Unload then reload a module (shows diff)",
+ "builtin": false,
+ "example": "module-reload"
+ },
+ "module-reset": {
+ "module": "module-modmgr",
+ "file": "modules/core/module-modmgr.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "module-reset <name> — Reset a module to a clean state (clears its state, keeps commands)",
+ "builtin": false,
+ "example": "module-reset"
+ },
+ "module-stats": {
+ "module": "module-modmgr",
+ "file": "modules/core/module-modmgr.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "module-stats — Show aggregate module statistics (loaded/unloaded/lazy/failed/disabled + total size)",
+ "builtin": false,
+ "example": "module-stats"
+ },
+ "module-unload": {
+ "module": "module-modmgr",
+ "file": "modules/core/module-modmgr.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "module-unload <name> — Mark a module as unloaded",
+ "builtin": false,
+ "example": "module-unload"
+ },
+ "modules": {
+ "module": "module-modmgr",
+ "file": "modules/core/module-modmgr.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "modules — Open the module manager GUI",
+ "builtin": false,
+ "example": "modules"
+ },
+ "modules-list": {
+ "module": "module-modmgr",
+ "file": "modules/core/module-modmgr.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "modules-list [core|ext] — List all available modules from the module index",
+ "builtin": false,
+ "example": "modules-list"
+ },
+ "monitor": {
+ "module": "module-servermon",
+ "file": "modules/core/module-servermon.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "monitor — Toggle auto-monitoring (checks every 60s)",
+ "builtin": false,
+ "example": "monitor"
+ },
+ "mv": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "mv <source> <dest> — Move file or directory",
+ "builtin": false,
+ "example": "mv"
+ },
+ "node": {
+ "module": "console",
+ "file": "modules/ext/console.js",
+ "tier": "ext-auto",
+ "category": "dev",
+ "desc": "node <path> — Alias for js-run (executes a JS file from VFS)",
+ "builtin": false,
+ "example": "node"
+ },
+ "nvfs-alias": {
+ "module": "module-neural-vfs",
+ "file": "modules/core/module-neural-vfs.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "nvfs-alias list|add|remove|clear — Manage concept aliases",
+ "builtin": false,
+ "example": "nvfs-alias"
+ },
+ "nvfs-cancel": {
+ "module": "module-neural-vfs",
+ "file": "modules/core/module-neural-vfs.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "nvfs-cancel — Cancel running index operation",
+ "builtin": false,
+ "example": "nvfs-cancel"
+ },
+ "nvfs-config": {
+ "module": "module-neural-vfs",
+ "file": "modules/core/module-neural-vfs.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "nvfs-config [key] [value] — View or set Neural VFS config (fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index, deep-model, exclude-patterns, exclude-dirs, exclude-files, include-only)",
+ "builtin": false,
+ "example": "nvfs-config"
+ },
+ "nvfs-index": {
+ "module": "module-neural-vfs",
+ "file": "modules/core/module-neural-vfs.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "nvfs-index [--deep] — Scan VFS and build semantic concept index",
+ "builtin": false,
+ "example": "nvfs-index"
+ },
+ "nvfs-index-graph": {
+ "module": "module-neural-vfs",
+ "file": "modules/core/module-neural-vfs.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "nvfs-index-graph — command from module-neural-vfs",
+ "builtin": false,
+ "example": "nvfs-index-graph"
+ },
+ "nvfs-resolve": {
+ "module": "module-neural-vfs",
+ "file": "modules/core/module-neural-vfs.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "nvfs-resolve <concept> — Find files related to a concept",
+ "builtin": false,
+ "example": "nvfs-resolve"
+ },
+ "nvfs-save": {
+ "module": "module-neural-vfs",
+ "file": "modules/core/module-neural-vfs.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "nvfs-save — Force-save current in-memory index to VFS (survives refresh)",
+ "builtin": false,
+ "example": "nvfs-save"
+ },
+ "nvfs-status": {
+ "module": "module-neural-vfs",
+ "file": "modules/core/module-neural-vfs.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "nvfs-status — Show semantic index stats and last scan time",
+ "builtin": false,
+ "example": "nvfs-status"
+ },
+ "nvfs-sync": {
+ "module": "module-neural-vfs",
+ "file": "modules/core/module-neural-vfs.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "nvfs-sync [--deep] — Incremental background sync (auto-sync dirty files)",
+ "builtin": false,
+ "example": "nvfs-sync"
+ },
+ "nvfs-tree": {
+ "module": "module-neural-vfs",
+ "file": "modules/core/module-neural-vfs.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "nvfs-tree [concept] — Show concept-to-file mapping as a tree",
+ "builtin": false,
+ "example": "nvfs-tree"
+ },
+ "ollama": {
+ "module": "module-ollama",
+ "file": "modules/core/module-ollama.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ollama — Open the Ollama model manager UI",
+ "builtin": false,
+ "example": "ollama"
+ },
+ "ollamaconnect": {
+ "module": "module-ollama",
+ "file": "modules/core/module-ollama.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ollamaconnect — Connect to the Ollama server",
+ "builtin": false,
+ "example": "ollamaconnect"
+ },
+ "ollamadisconnect": {
+ "module": "module-ollama",
+ "file": "modules/core/module-ollama.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ollamadisconnect — Disconnect from the Ollama server",
+ "builtin": false,
+ "example": "ollamadisconnect"
+ },
+ "ollamamodels": {
+ "module": "module-ollama",
+ "file": "modules/core/module-ollama.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ollamamodels — List all available models from Ollama",
+ "builtin": false,
+ "example": "ollamamodels"
+ },
+ "ollamasetmodel": {
+ "module": "module-ollama",
+ "file": "modules/core/module-ollama.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ollamasetmodel <name> — Set the active Ollama model",
+ "builtin": false,
+ "example": "ollamasetmodel"
+ },
+ "ollamasetproxy": {
+ "module": "module-ollama",
+ "file": "modules/core/module-ollama.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "ollamasetproxy [endpoint] — View or change the Ollama endpoint URL",
+ "builtin": false,
+ "example": "ollamasetproxy"
+ },
+ "orchestrate": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrate <task> — Delegate a task to an available agent (auto-spawns if needed)",
+ "builtin": false,
+ "example": "orchestrate"
+ },
+ "orchestrate-decompose": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrate-decompose <task> — Preview how a task would be decomposed into sub-tasks",
+ "builtin": false,
+ "example": "orchestrate-decompose"
+ },
+ "orchestrate-parallel": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrate-parallel <task> — Decompose and delegate sub-tasks in parallel (auto-splits on \"and\"/\"then\")",
+ "builtin": false,
+ "example": "orchestrate-parallel"
+ },
+ "orchestrate-result": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrate-result — Show the last parallel batch results (stored in VFS)",
+ "builtin": false,
+ "example": "orchestrate-result"
+ },
+ "orchestrator-config": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrator-config <key> <value> — Tune: spawn-limit, spawn-strategy, heartbeat-timeout, idle-timeout, restart-policy, a2a-polling, a2a-poll-interval, heartbeat-check, idle-sweep-interval, dead-purge-timeout",
+ "builtin": false,
+ "example": "orchestrator-config"
+ },
+ "orchestrator-kill": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrator-kill <agent-id> — Terminate an agent and revoke leases",
+ "builtin": false,
+ "example": "orchestrator-kill"
+ },
+ "orchestrator-list": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrator-list — List all spawned agents with full details",
+ "builtin": false,
+ "example": "orchestrator-list"
+ },
+ "orchestrator-purge": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrator-purge — Remove dead agents (>60s since heartbeat)",
+ "builtin": false,
+ "example": "orchestrator-purge"
+ },
+ "orchestrator-restart-policy": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrator-restart-policy <agent-id> <on-failure|always|never> — Set per-agent restart policy",
+ "builtin": false,
+ "example": "orchestrator-restart-policy"
+ },
+ "orchestrator-schedule": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrator-schedule <role> <interval_seconds> <task...> — Schedule a recurring task delegation",
+ "builtin": false,
+ "example": "orchestrator-schedule"
+ },
+ "orchestrator-schedules": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrator-schedules — List all active recurring schedules",
+ "builtin": false,
+ "example": "orchestrator-schedules"
+ },
+ "orchestrator-sleep": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrator-sleep <agent-id> — Suspend agent to VFS",
+ "builtin": false,
+ "example": "orchestrator-sleep"
+ },
+ "orchestrator-spawn": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrator-spawn [role] [capabilities...] — Manually spawn a new agent",
+ "builtin": false,
+ "example": "orchestrator-spawn"
+ },
+ "orchestrator-status": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrator-status — Show agent pool status and config",
+ "builtin": false,
+ "example": "orchestrator-status"
+ },
+ "orchestrator-unschedule": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrator-unschedule <id> — Remove a recurring schedule",
+ "builtin": false,
+ "example": "orchestrator-unschedule"
+ },
+ "orchestrator-wake": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "orchestrator-wake <agent-id> — Restore agent from VFS sleep",
+ "builtin": false,
+ "example": "orchestrator-wake"
+ },
+ "pipeline": {
+ "module": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "category": "agents",
+ "desc": "pipeline <test|deploy|verify|history|config> — Autonomous CI/CD: test agents/queue/modules, deploy (test→auto-fix→verify), history (show runs), config (show config)",
+ "builtin": false,
+ "example": "pipeline"
+ },
+ "predict": {
+ "module": "module-predictor",
+ "file": "modules/core/module-predictor.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "predict <task description> — Predict task duration, tool turns, and confidence",
+ "builtin": false,
+ "example": "predict"
+ },
+ "predictor-config": {
+ "module": "module-predictor",
+ "file": "modules/core/module-predictor.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "predictor-config <key> <value> — Tune: confidence-threshold, min-data-points",
+ "builtin": false,
+ "example": "predictor-config"
+ },
+ "predictor-status": {
+ "module": "module-predictor",
+ "file": "modules/core/module-predictor.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "predictor-status — Model accuracy, data points, prediction count",
+ "builtin": false,
+ "example": "predictor-status"
+ },
+ "preview": {
+ "module": "filemgr",
+ "file": "modules/ext/filemgr.js",
+ "tier": "critical-ext-bundle",
+ "category": "filesystem",
+ "desc": "preview <path> — Preview a specific file",
+ "builtin": false,
+ "example": "preview"
+ },
+ "printf": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "printf <format> [args...] — Formatted print (%s, %d, %f, \\\\n, \\\\t)",
+ "builtin": false,
+ "example": "printf"
+ },
+ "put": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "put <file> <content> — Create or overwrite a file",
+ "builtin": false,
+ "example": "put"
+ },
+ "pwd": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "pwd — Print current working directory",
+ "builtin": false,
+ "example": "pwd"
+ },
+ "removemod": {
+ "module": "module-modmgr",
+ "file": "modules/core/module-modmgr.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "removemod <name> — Remove a loaded module by name",
+ "builtin": false,
+ "example": "removemod"
+ },
+ "reset": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "reset [mem|vfs|config|all] — Global reset command (default: mem)",
+ "builtin": false,
+ "example": "reset"
+ },
+ "reset-mem": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "reset-mem — Reset AI memory (logs, context, profile, guardrails) without losing VFS or config",
+ "builtin": false,
+ "example": "reset-mem"
+ },
+ "reset-vfs": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "reset-vfs — Alias for reset vfs",
+ "builtin": false,
+ "example": "reset-vfs"
+ },
+ "rm": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "rm <file> — Remove a file (moved to trash); use -r/-rf for directories",
+ "builtin": false,
+ "example": "rm"
+ },
+ "rmdir": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "rmdir <dir> — Remove a directory",
+ "builtin": false,
+ "example": "rmdir"
+ },
+ "rollback": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "rollback <name> — Restore VFS from a checkpoint",
+ "builtin": false,
+ "example": "rollback"
+ },
+ "s": {
+ "module": "module-searxng",
+ "file": "modules/core/module-searxng.js",
+ "tier": "core-bundle",
+ "category": "web",
+ "desc": "s — command from module-searxng",
+ "builtin": false,
+ "example": "s"
+ },
+ "sandbox-config": {
+ "module": "module-sandbox",
+ "file": "modules/core/module-sandbox.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "sandbox-config <key> <value> — Tune: max-workers, min-workers, pool-timeout",
+ "builtin": false,
+ "example": "sandbox-config"
+ },
+ "sandbox-run": {
+ "module": "module-sandbox",
+ "file": "modules/core/module-sandbox.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "sandbox-run <task> — Run a task in the sandbox (automatic routing)",
+ "builtin": false,
+ "example": "sandbox-run"
+ },
+ "sandbox-status": {
+ "module": "module-sandbox",
+ "file": "modules/core/module-sandbox.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "sandbox-status — Show sandbox pool health and task metrics",
+ "builtin": false,
+ "example": "sandbox-status"
+ },
+ "script": {
+ "module": "shell",
+ "file": "modules/ext/shell.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "script <path.sh> — Run a shell script from VFS",
+ "builtin": false,
+ "example": "script"
+ },
+ "search": {
+ "module": "module-searxng",
+ "file": "modules/core/module-searxng.js",
+ "tier": "core-bundle",
+ "category": "web",
+ "desc": "search <query> [--summarize/-s] [--fetch/-f] [--images/-i] [--videos/-v] — Search using SearXNG; --summarize/-s adds AI overview, --fetch/-f fetches page content, --images/-i searches for images, --videos/-v searches for videos",
+ "builtin": false,
+ "example": "search"
+ },
+ "secret-del": {
+ "module": "secret-vault",
+ "file": "modules/core/secret-vault.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "secret-del <name> — Remove a stored secret",
+ "builtin": false,
+ "example": "secret-del"
+ },
+ "secret-get": {
+ "module": "secret-vault",
+ "file": "modules/core/secret-vault.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "secret-get <name> — Decrypt and display a secret value",
+ "builtin": false,
+ "example": "secret-get"
+ },
+ "secret-list": {
+ "module": "secret-vault",
+ "file": "modules/core/secret-vault.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "secret-list — List all stored secret names",
+ "builtin": false,
+ "example": "secret-list"
+ },
+ "secret-lock": {
+ "module": "secret-vault",
+ "file": "modules/core/secret-vault.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "secret-lock — Lock the vault (clear derived key from memory)",
+ "builtin": false,
+ "example": "secret-lock"
+ },
+ "secret-set": {
+ "module": "secret-vault",
+ "file": "modules/core/secret-vault.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "secret-set <name> <value> — Encrypt and store a secret",
+ "builtin": false,
+ "example": "secret-set"
+ },
+ "secret-setpin": {
+ "module": "secret-vault",
+ "file": "modules/core/secret-vault.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "secret-setpin <pin> — Set or change the vault PIN code",
+ "builtin": false,
+ "example": "secret-setpin"
+ },
+ "secret-status": {
+ "module": "secret-vault",
+ "file": "modules/core/secret-vault.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "secret-status — Show vault status (locked/unlocked/pin set/open mode)",
+ "builtin": false,
+ "example": "secret-status"
+ },
+ "secret-unlock": {
+ "module": "secret-vault",
+ "file": "modules/core/secret-vault.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "secret-unlock <pin> — Unlock the vault with a PIN code",
+ "builtin": false,
+ "example": "secret-unlock"
+ },
+ "security": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "Extracted from newOS4 index.html — WebX11Terminal",
+ "builtin": true,
+ "example": "security"
+ },
+ "security-destructive": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "security-destructive — command from terminal",
+ "builtin": true,
+ "example": "security-destructive"
+ },
+ "security-flag": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "security-flag — command from terminal",
+ "builtin": true,
+ "example": "security-flag"
+ },
+ "security-level": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "Extracted from newOS4 index.html — WebX11Terminal",
+ "builtin": true,
+ "example": "security-level"
+ },
+ "security-system": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "security-system — command from terminal",
+ "builtin": true,
+ "example": "security-system"
+ },
+ "security-whitelist": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "security-whitelist — command from terminal",
+ "builtin": true,
+ "example": "security-whitelist"
+ },
+ "sed": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "sed <expr> [file] — Stream editor; s/pat/rep/[g][i][p], /pat/d, Nd, $d; -i write in place; -n quiet mode",
+ "builtin": false,
+ "example": "sed"
+ },
+ "seq": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "seq <last> or seq <first> <last> — Generate sequence of numbers",
+ "builtin": false,
+ "example": "seq"
+ },
+ "server": {
+ "module": "module-servermon",
+ "file": "modules/core/module-servermon.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "server <name> <url> — Add a server to monitor",
+ "builtin": false,
+ "example": "server"
+ },
+ "server-check": {
+ "module": "module-servermon",
+ "file": "modules/core/module-servermon.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "server-check <name-or-index> — Check a server status",
+ "builtin": false,
+ "example": "server-check"
+ },
+ "server-remove": {
+ "module": "module-servermon",
+ "file": "modules/core/module-servermon.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "server-remove <name-or-index> — Remove a server",
+ "builtin": false,
+ "example": "server-remove"
+ },
+ "servers": {
+ "module": "module-servermon",
+ "file": "modules/core/module-servermon.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "servers — List all monitored servers",
+ "builtin": false,
+ "example": "servers"
+ },
+ "session-delete": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "session-delete <name> — Delete a saved session",
+ "builtin": false,
+ "example": "session-delete"
+ },
+ "session-export": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "session-export — command from module-fs",
+ "builtin": false,
+ "example": "session-export"
+ },
+ "session-import": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "session-import — command from module-fs",
+ "builtin": false,
+ "example": "session-import"
+ },
+ "session-list": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "session-list — List all saved sessions",
+ "builtin": false,
+ "example": "session-list"
+ },
+ "session-load": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "session-load <name> — Load a saved session",
+ "builtin": false,
+ "example": "session-load"
+ },
+ "session-new": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "session-new — Start a fresh session (auto-saves current, clears AI context)",
+ "builtin": false,
+ "example": "session-new"
+ },
+ "session-save": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "session-save <name> — Save current session (path + terminal history)",
+ "builtin": false,
+ "example": "session-save"
+ },
+ "set": {
+ "module": "shell",
+ "file": "modules/ext/shell.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "set — List all shell variables",
+ "builtin": false,
+ "example": "set"
+ },
+ "setfont": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "setfont <8-32> — Alias for font-size",
+ "builtin": false,
+ "example": "setfont"
+ },
+ "sh": {
+ "module": "shell",
+ "file": "modules/ext/shell.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "sh <path.sh> — Run a shell script from VFS (alias for script)",
+ "builtin": false,
+ "example": "sh"
+ },
+ "shadow": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "shadow — command from module-aiassist",
+ "builtin": false,
+ "example": "shadow"
+ },
+ "shadow-commit": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "shadow-commit — command from module-aiassist",
+ "builtin": false,
+ "example": "shadow-commit"
+ },
+ "shadow-rollback": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "shadow-rollback — command from module-aiassist",
+ "builtin": false,
+ "example": "shadow-rollback"
+ },
+ "skill-audit": {
+ "module": "skill-pack-core",
+ "file": "modules/ext/skill-pack-core.js",
+ "tier": "ext-auto",
+ "category": "ai",
+ "desc": "skill-audit — Audit skills for stale, duplicate, and broken references",
+ "builtin": false,
+ "example": "skill-audit"
+ },
+ "skill-create": {
+ "module": "skill-pack-core",
+ "file": "modules/ext/skill-pack-core.js",
+ "tier": "ext-auto",
+ "category": "ai",
+ "desc": "skill-create <name> <desc> \"<steps>\" [tags] — Create a new skill",
+ "builtin": false,
+ "example": "skill-create"
+ },
+ "skill-delete": {
+ "module": "skill-pack-core",
+ "file": "modules/ext/skill-pack-core.js",
+ "tier": "ext-auto",
+ "category": "ai",
+ "desc": "skill-delete <name> — Delete a skill",
+ "builtin": false,
+ "example": "skill-delete"
+ },
+ "skill-export": {
+ "module": "skill-pack-core",
+ "file": "modules/ext/skill-pack-core.js",
+ "tier": "ext-auto",
+ "category": "ai",
+ "desc": "skill-export <name> — Export a skill as JSON",
+ "builtin": false,
+ "example": "skill-export"
+ },
+ "skill-import": {
+ "module": "skill-pack-core",
+ "file": "modules/ext/skill-pack-core.js",
+ "tier": "ext-auto",
+ "category": "ai",
+ "desc": "skill-import <json> — Import a skill from JSON string",
+ "builtin": false,
+ "example": "skill-import"
+ },
+ "skill-list": {
+ "module": "skill-pack-core",
+ "file": "modules/ext/skill-pack-core.js",
+ "tier": "ext-auto",
+ "category": "ai",
+ "desc": "skill-list [category] — List all skills, optionally filtered by category",
+ "builtin": false,
+ "example": "skill-list"
+ },
+ "skill-pack-core": {
+ "module": "skill-pack-core",
+ "file": "modules/ext/skill-pack-core.js",
+ "tier": "ext-auto",
+ "category": "ai",
+ "desc": "Seed and manage built-in core skills (navigation, memory, search, modules, backup, agents, webdav, security, etc.)",
+ "builtin": false,
+ "example": "skill-pack-core"
+ },
+ "skill-patch": {
+ "module": "skill-pack-core",
+ "file": "modules/ext/skill-pack-core.js",
+ "tier": "ext-auto",
+ "category": "ai",
+ "desc": "skill-patch <name> \"<content>\" — Replace a skill's full content",
+ "builtin": false,
+ "example": "skill-patch"
+ },
+ "skill-restore": {
+ "module": "skill-pack-core",
+ "file": "modules/ext/skill-pack-core.js",
+ "tier": "ext-auto",
+ "category": "ai",
+ "desc": "skill-restore <name> — Restore an archived skill",
+ "builtin": false,
+ "example": "skill-restore"
+ },
+ "skill-sync": {
+ "module": "skill-pack-core",
+ "file": "modules/ext/skill-pack-core.js",
+ "tier": "ext-auto",
+ "category": "ai",
+ "desc": "skill-sync — Rebuild skills index from /skills/ directory",
+ "builtin": false,
+ "example": "skill-sync"
+ },
+ "skill-validate": {
+ "module": "skill-pack-core",
+ "file": "modules/ext/skill-pack-core.js",
+ "tier": "ext-auto",
+ "category": "ai",
+ "desc": "skill-validate <name> — Validate skill structure and get confidence score",
+ "builtin": false,
+ "example": "skill-validate"
+ },
+ "skill-view": {
+ "module": "skill-pack-core",
+ "file": "modules/ext/skill-pack-core.js",
+ "tier": "ext-auto",
+ "category": "ai",
+ "desc": "skill-view <name> — View a skill's full content",
+ "builtin": false,
+ "example": "skill-view"
+ },
+ "skill-webdav-sync": {
+ "module": "skill-pack-core",
+ "file": "modules/ext/skill-pack-core.js",
+ "tier": "ext-auto",
+ "category": "ai",
+ "desc": "skill-webdav-sync — Push all skills to WebDAV as JSON files",
+ "builtin": false,
+ "example": "skill-webdav-sync"
+ },
+ "sleep": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "sleep <seconds> — Delay execution with countdown",
+ "builtin": false,
+ "example": "sleep"
+ },
+ "smart-ask": {
+ "module": "module-smartctx",
+ "file": "modules/core/module-smartctx.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "smart-ask <question> — Ask a question using the active context",
+ "builtin": false,
+ "example": "smart-ask"
+ },
+ "smart-prompt": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "Extracted from newOS4 index.html — SystemModule",
+ "builtin": true,
+ "example": "smart-prompt"
+ },
+ "sort": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "sort <file> — Sort lines of a file alphabetically",
+ "builtin": false,
+ "example": "sort"
+ },
+ "speak": {
+ "module": "voice",
+ "file": "modules/ext/voice.js",
+ "tier": "ext-auto",
+ "category": "ui",
+ "desc": "speak [text] — Text-to-speech. No args = toggle auto-speak for AI responses",
+ "builtin": false,
+ "example": "speak"
+ },
+ "startup-modules": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "Extracted from newOS4 index.html — ModuleManager",
+ "builtin": true,
+ "example": "startup-modules"
+ },
+ "status": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "status — Display system status (network, files, modules, Ollama)",
+ "builtin": false,
+ "example": "status"
+ },
+ "sysmsg": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "sysmsg — command from terminal",
+ "builtin": true,
+ "example": "sysmsg"
+ },
+ "system-health": {
+ "module": "module-servermon",
+ "file": "modules/core/module-servermon.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "system-health — Show tool latency, module load times, VFS size, and error rates",
+ "builtin": false,
+ "example": "system-health"
+ },
+ "tail": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "tail [-f] [-n N] <file> — Show last N lines; -f follows live changes (Ctrl+C/Esc to stop)",
+ "builtin": false,
+ "example": "tail"
+ },
+ "tar": {
+ "module": "cryptogzip",
+ "file": "modules/ext/cryptogzip.js",
+ "tier": "ext-auto",
+ "category": "system",
+ "desc": "tar <src-dir> <out.tar[.gz]> — Bundle a VFS directory into a tar archive (gzip if .gz)",
+ "builtin": false,
+ "example": "tar"
+ },
+ "test-net": {
+ "module": "module-servermon",
+ "file": "modules/core/module-servermon.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "test-net — Check proxy API, Ollama, and internet connectivity",
+ "builtin": false,
+ "example": "test-net"
+ },
+ "theme": {
+ "module": "theme",
+ "file": "modules/ext/theme.js",
+ "tier": "ext-auto",
+ "category": "ui",
+ "desc": "theme <set|list|new|delete|export|preview|keep> [...] — Manage and preview visual themes",
+ "builtin": false,
+ "example": "theme"
+ },
+ "three": {
+ "module": "three",
+ "file": "modules/ext/three.js",
+ "tier": "ext-auto",
+ "category": "ui",
+ "desc": "three <subcommand> [...] — 3D scene builder (Three.js). Run `three help` for full list.",
+ "builtin": false,
+ "example": "three"
+ },
+ "touch": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "touch <file> — Create empty file or update timestamp",
+ "builtin": false,
+ "example": "touch"
+ },
+ "trash": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "trash <file> — Move a file to trash",
+ "builtin": false,
+ "example": "trash"
+ },
+ "trash-dir": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "trash-dir <dir> — Move a directory to trash",
+ "builtin": false,
+ "example": "trash-dir"
+ },
+ "tree": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "tree — Display directory structure as a tree",
+ "builtin": false,
+ "example": "tree"
+ },
+ "tutorial": {
+ "module": "tutorial",
+ "file": "modules/ext/tutorial.js",
+ "tier": "ext-auto",
+ "category": "learning",
+ "desc": "tutorial [start|skip|status] — Interactive guided tour of newOS4 (6 quests)",
+ "builtin": false,
+ "example": "tutorial"
+ },
+ "unalias": {
+ "module": "alias",
+ "file": "modules/ext/alias.js",
+ "tier": "critical-ext-bundle",
+ "category": "general",
+ "desc": "Remove a user-defined alias (usage: unalias <name>)",
+ "builtin": false,
+ "example": "unalias"
+ },
+ "uniq": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "uniq <file> — Filter adjacent duplicate lines from a file",
+ "builtin": false,
+ "example": "uniq"
+ },
+ "unset": {
+ "module": "shell",
+ "file": "modules/ext/shell.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "unset <var> — Remove a shell variable",
+ "builtin": false,
+ "example": "unset"
+ },
+ "untar": {
+ "module": "cryptogzip",
+ "file": "modules/ext/cryptogzip.js",
+ "tier": "ext-auto",
+ "category": "system",
+ "desc": "untar <path.tar[.gz]> [dest-dir] — Extract a tar archive into VFS (auto-detects gzip)",
+ "builtin": false,
+ "example": "untar"
+ },
+ "upload": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "upload — Upload a file from your device",
+ "builtin": false,
+ "example": "upload"
+ },
+ "uptime": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "uptime — Show time since page load + last save time",
+ "builtin": false,
+ "example": "uptime"
+ },
+ "usage": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "usage — command from terminal",
+ "builtin": true,
+ "example": "usage"
+ },
+ "vaultmind-connect": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "vaultmind-connect [testnet|mainnet] — Connect VaultMind wallet using hyperliquid_key from SecretVault",
+ "builtin": false,
+ "example": "vaultmind-connect"
+ },
+ "vaultmind-query": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "vaultmind-query <prices|portfolio|transactions|copy-trade|chat> [message] — Query VaultMind API",
+ "builtin": false,
+ "example": "vaultmind-query"
+ },
+ "vaultmind-status": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "vaultmind-status — Check VaultMind session status",
+ "builtin": false,
+ "example": "vaultmind-status"
+ },
+ "version": {
+ "module": "module-system",
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "version — Show version, build date, and cache key",
+ "builtin": false,
+ "example": "version"
+ },
+ "vfs": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "vfs — command from terminal",
+ "builtin": true,
+ "example": "vfs"
+ },
+ "vfs-clean": {
+ "module": "terminal",
+ "file": "modules/core/terminal.js",
+ "tier": "core-bundle",
+ "category": "system",
+ "desc": "vfs-clean — command from terminal",
+ "builtin": true,
+ "example": "vfs-clean"
+ },
+ "vfs-export": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "vfs-export — Export the entire virtual filesystem as JSON",
+ "builtin": false,
+ "example": "vfs-export"
+ },
+ "vfs-import": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "vfs-import — Import a previously exported VFS JSON file",
+ "builtin": false,
+ "example": "vfs-import"
+ },
+ "vfs-size": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "vfs-size — command from module-fs",
+ "builtin": false,
+ "example": "vfs-size"
+ },
+ "videos": {
+ "module": "module-searxng",
+ "file": "modules/core/module-searxng.js",
+ "tier": "core-bundle",
+ "category": "web",
+ "desc": "videos <query> — Search for videos",
+ "builtin": false,
+ "example": "videos"
+ },
+ "voice": {
+ "module": "voice",
+ "file": "modules/ext/voice.js",
+ "tier": "ext-auto",
+ "category": "ui",
+ "desc": "voice [stop] — Start voice recording (Whisper STT), or type \"voice stop\" to cancel",
+ "builtin": false,
+ "example": "voice"
+ },
+ "wallet-lookup": {
+ "module": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "wallet-lookup <address> — Look up Hyperliquid wallet summary",
+ "builtin": false,
+ "example": "wallet-lookup"
+ },
+ "wc": {
+ "module": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "category": "filesystem",
+ "desc": "wc <file> — Count lines, words, and bytes",
+ "builtin": false,
+ "example": "wc"
+ },
+ "webdav-backup": {
+ "module": "webdav",
+ "file": "modules/ext/webdav.js",
+ "tier": "ext-auto",
+ "category": "cloud",
+ "desc": "Backup entire VFS to WebDAV as a dated JSON snapshot",
+ "builtin": false,
+ "example": "webdav-backup"
+ },
+ "webdav-backup-list": {
+ "module": "webdav",
+ "file": "modules/ext/webdav.js",
+ "tier": "ext-auto",
+ "category": "cloud",
+ "desc": "List available backup files on WebDAV",
+ "builtin": false,
+ "example": "webdav-backup-list"
+ },
+ "webdav-browser": {
+ "module": "webdav",
+ "file": "modules/ext/webdav.js",
+ "tier": "ext-auto",
+ "category": "cloud",
+ "desc": "webdav-browser [path] — Modern WebDAV file-tree browser with bulk operations",
+ "builtin": false,
+ "example": "webdav-browser"
+ },
+ "webdav-config": {
+ "module": "webdav",
+ "file": "modules/ext/webdav.js",
+ "tier": "ext-auto",
+ "category": "cloud",
+ "desc": "webdav-config [url user pass] — Configure WebDAV. No args = auto-detect from current page URL",
+ "builtin": false,
+ "example": "webdav-config"
+ },
+ "webdav-login": {
+ "module": "webdav",
+ "file": "modules/ext/webdav.js",
+ "tier": "ext-auto",
+ "category": "cloud",
+ "desc": "webdav-login [username] [password] — Open the login panel (Telegram or password) in THIS browser and configure WebDAV",
+ "builtin": false,
+ "example": "webdav-login"
+ },
+ "webdav-ls": {
+ "module": "webdav",
+ "file": "modules/ext/webdav.js",
+ "tier": "ext-auto",
+ "category": "cloud",
+ "desc": "List files/directories on the WebDAV remote",
+ "builtin": false,
+ "example": "webdav-ls"
+ },
+ "webdav-mkdir": {
+ "module": "webdav",
+ "file": "modules/ext/webdav.js",
+ "tier": "ext-auto",
+ "category": "cloud",
+ "desc": "webdav-mkdir <path> — Create a remote directory",
+ "builtin": false,
+ "example": "webdav-mkdir"
+ },
+ "webdav-move": {
+ "module": "webdav",
+ "file": "modules/ext/webdav.js",
+ "tier": "ext-auto",
+ "category": "cloud",
+ "desc": "webdav-move <old> <new> — Alias for webdav-rename",
+ "builtin": false,
+ "example": "webdav-move"
+ },
+ "webdav-pull": {
+ "module": "webdav",
+ "file": "modules/ext/webdav.js",
+ "tier": "ext-auto",
+ "category": "cloud",
+ "desc": "Download a WebDAV file to VFS",
+ "builtin": false,
+ "example": "webdav-pull"
+ },
+ "webdav-push": {
+ "module": "webdav",
+ "file": "modules/ext/webdav.js",
+ "tier": "ext-auto",
+ "category": "cloud",
+ "desc": "Upload a VFS file to WebDAV",
+ "builtin": false,
+ "example": "webdav-push"
+ },
+ "webdav-rename": {
+ "module": "webdav",
+ "file": "modules/ext/webdav.js",
+ "tier": "ext-auto",
+ "category": "cloud",
+ "desc": "webdav-rename <old> <new> — Rename or move a remote file/dir",
+ "builtin": false,
+ "example": "webdav-rename"
+ },
+ "webdav-restore": {
+ "module": "webdav",
+ "file": "modules/ext/webdav.js",
+ "tier": "ext-auto",
+ "category": "cloud",
+ "desc": "Restore VFS from a WebDAV backup file",
+ "builtin": false,
+ "example": "webdav-restore"
+ },
+ "webdav-rm": {
+ "module": "webdav",
+ "file": "modules/ext/webdav.js",
+ "tier": "ext-auto",
+ "category": "cloud",
+ "desc": "webdav-rm <path> — Delete a remote file or directory",
+ "builtin": false,
+ "example": "webdav-rm"
+ },
+ "webdav-status": {
+ "module": "webdav",
+ "file": "modules/ext/webdav.js",
+ "tier": "ext-auto",
+ "category": "cloud",
+ "desc": "Show current WebDAV configuration status",
+ "builtin": false,
+ "example": "webdav-status"
+ },
+ "webx": {
+ "module": "module-aiasm",
+ "file": "modules/core/module-aiasm.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "webx <question> — Alias for webxai",
+ "builtin": false,
+ "example": "webx"
+ },
+ "webxai": {
+ "module": "module-aiasm",
+ "file": "modules/core/module-aiasm.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "webxai <question> — Send a question to the AI assistant",
+ "builtin": false,
+ "example": "webxai"
+ },
+ "wget": {
+ "module": "cryptogzip",
+ "file": "modules/ext/cryptogzip.js",
+ "tier": "ext-auto",
+ "category": "system",
+ "desc": "wget <url> [filename] — Download a URL and save to VFS",
+ "builtin": false,
+ "example": "wget"
+ },
+ "which": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "which <name> — Locate a command in the module registry",
+ "builtin": false,
+ "example": "which"
+ },
+ "whoami": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "whoami — Show current user name",
+ "builtin": false,
+ "example": "whoami"
+ },
+ "world-model": {
+ "module": "module-world-model",
+ "file": "modules/core/module-world-model.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "world-model — Show full world model as structured output",
+ "builtin": false,
+ "example": "world-model"
+ },
+ "world-model-ask": {
+ "module": "module-world-model",
+ "file": "modules/core/module-world-model.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "world-model-ask <question> — Query the model (e.g. \"what changed since yesterday\")",
+ "builtin": false,
+ "example": "world-model-ask"
+ },
+ "world-model-config": {
+ "module": "module-world-model",
+ "file": "modules/core/module-world-model.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "world-model-config <key> <value> — Tune: refresh-interval, max-projects",
+ "builtin": false,
+ "example": "world-model-config"
+ },
+ "world-model-refresh": {
+ "module": "module-world-model",
+ "file": "modules/core/module-world-model.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "world-model-refresh — Force re-synthesis from all subsystems",
+ "builtin": false,
+ "example": "world-model-refresh"
+ },
+ "world-model-summary": {
+ "module": "module-world-model",
+ "file": "modules/core/module-world-model.js",
+ "tier": "core-bundle",
+ "category": "ai",
+ "desc": "world-model-summary — One-line system self-awareness statement",
+ "builtin": false,
+ "example": "world-model-summary"
+ },
+ "wpm": {
+ "module": "wpm",
+ "file": "modules/ext/wpm.js",
+ "tier": "ext-auto",
+ "category": "system",
+ "desc": "wpm <install|list|search|update|remove|info> [...] — Web Package Manager for installing and managing modules",
+ "builtin": false,
+ "example": "wpm"
+ },
+ "wx": {
+ "module": "module-aiasm",
+ "file": "modules/core/module-aiasm.js",
+ "tier": "core-bundle",
+ "category": "general",
+ "desc": "wx <question> — Alias for webxai",
+ "builtin": false,
+ "example": "wx"
+ },
+ "yes": {
+ "module": "busybox",
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "category": "shell",
+ "desc": "yes [string] — Output repeated string (default \"y\", capped at 50)",
+ "builtin": false,
+ "example": "yes"
+ }
+ },
+ "modules": {
+ "alias": {
+ "file": "modules/ext/alias.js",
+ "tier": "critical-ext-bundle",
+ "commands": [
+ "alias",
+ "unalias"
+ ],
+ "lines": 119
+ },
+ "busybox": {
+ "file": "modules/ext/busybox.js",
+ "tier": "critical-ext-bundle",
+ "commands": [
+ "base64",
+ "basename",
+ "cal",
+ "cut",
+ "date",
+ "dirname",
+ "env",
+ "expand",
+ "hostname",
+ "printf",
+ "seq",
+ "sleep",
+ "uniq",
+ "which",
+ "whoami",
+ "yes"
+ ],
+ "lines": 415
+ },
+ "calc": {
+ "file": "modules/ext/calc.js",
+ "tier": "ext-auto",
+ "commands": [
+ "calc"
+ ],
+ "lines": 107
+ },
+ "console": {
+ "file": "modules/ext/console.js",
+ "tier": "ext-auto",
+ "commands": [
+ "js",
+ "js-run",
+ "node"
+ ],
+ "lines": 96
+ },
+ "cron": {
+ "file": "modules/ext/cron.js",
+ "tier": "ext-auto",
+ "commands": [
+ "cron-clear",
+ "cron-every",
+ "cron-list",
+ "cron-stop"
+ ],
+ "lines": 111
+ },
+ "cryptogzip": {
+ "file": "modules/ext/cryptogzip.js",
+ "tier": "ext-auto",
+ "commands": [
+ "decrypt",
+ "encrypt",
+ "gunzip",
+ "gzip",
+ "tar",
+ "untar",
+ "wget"
+ ],
+ "lines": 599
+ },
+ "debug-panel": {
+ "file": "modules/ext/debug-panel.js",
+ "tier": "ext-auto",
+ "commands": [
+ "debug"
+ ],
+ "lines": 167
+ },
+ "diff": {
+ "file": "modules/ext/diff.js",
+ "tier": "critical-ext-bundle",
+ "commands": [
+ "diff"
+ ],
+ "lines": 288
+ },
+ "editor": {
+ "file": "modules/ext/editor.js",
+ "tier": "critical-ext-bundle",
+ "commands": [
+ "edit"
+ ],
+ "lines": 133
+ },
+ "filemgr": {
+ "file": "modules/ext/filemgr.js",
+ "tier": "critical-ext-bundle",
+ "commands": [
+ "files",
+ "preview"
+ ],
+ "lines": 987
+ },
+ "finder": {
+ "file": "modules/ext/finder.js",
+ "tier": "critical-ext-bundle",
+ "commands": [
+ "find"
+ ],
+ "lines": 48
+ },
+ "floating": {
+ "file": "modules/ext/floating.js",
+ "tier": "ext-auto",
+ "commands": [
+ "floating-add",
+ "floating-clear",
+ "floating-list",
+ "floating-pos",
+ "floating-remove",
+ "floating-toggle"
+ ],
+ "lines": 444
+ },
+ "kanban": {
+ "file": "modules/ext/kanban.js",
+ "tier": "ext-auto",
+ "commands": [
+ "kanban"
+ ],
+ "lines": 191
+ },
+ "media": {
+ "file": "modules/ext/media.js",
+ "tier": "ext-auto",
+ "commands": [
+ "analyze"
+ ],
+ "lines": 508
+ },
+ "mindmap": {
+ "file": "modules/ext/mindmap.js",
+ "tier": "ext-auto",
+ "commands": [
+ "mindmap"
+ ],
+ "lines": 1191
+ },
+ "module-agent-registry": {
+ "file": "modules/core/module-agent-registry.js",
+ "tier": "core-bundle",
+ "commands": [
+ "a2a-ack",
+ "a2a-poll",
+ "a2a-post",
+ "a2a-prune",
+ "a2a-reject",
+ "a2a-status",
+ "agent-list",
+ "agent-register",
+ "agent-status",
+ "agent-unregister",
+ "contract-list",
+ "contract-register"
+ ],
+ "lines": 474
+ },
+ "module-aiasm": {
+ "file": "modules/core/module-aiasm.js",
+ "tier": "core-bundle",
+ "commands": [
+ "webx",
+ "webxai",
+ "wx"
+ ],
+ "lines": 27
+ },
+ "module-aiassist": {
+ "file": "modules/core/module-aiassist.js",
+ "tier": "core-bundle",
+ "commands": [
+ "agent-loops",
+ "ai",
+ "ai-allow",
+ "ai-clear",
+ "ai-compress",
+ "ai-config",
+ "ai-context",
+ "ai-deny",
+ "ai-ignore",
+ "ai-model",
+ "ai-stop",
+ "ai-system",
+ "archive-list",
+ "archive-read",
+ "ask",
+ "bench",
+ "bench-compare",
+ "commit-approve",
+ "commit-deny",
+ "commit-status",
+ "debug",
+ "debug-ai",
+ "debug-ai-last",
+ "debug-ai-log",
+ "debug-ai-send",
+ "guardrail",
+ "guardrail-add",
+ "guardrail-remove",
+ "mem-debug",
+ "reset-mem",
+ "shadow",
+ "shadow-commit",
+ "shadow-rollback",
+ "vaultmind-connect",
+ "vaultmind-query",
+ "vaultmind-status",
+ "wallet-lookup"
+ ],
+ "lines": 4376
+ },
+ "module-chatstudio": {
+ "file": "modules/core/module-chatstudio.js",
+ "tier": "core-bundle",
+ "commands": [
+ "chat"
+ ],
+ "lines": 1138
+ },
+ "module-config": {
+ "file": "modules/core/module-config.js",
+ "tier": "core-bundle",
+ "commands": [
+ "config",
+ "config-delete",
+ "config-export",
+ "config-list",
+ "config-reset",
+ "config-set"
+ ],
+ "lines": 93
+ },
+ "module-debug-supervisor": {
+ "file": "modules/core/module-debug-supervisor.js",
+ "tier": "core-bundle",
+ "commands": [
+ "debug-analyze",
+ "debug-fix",
+ "debug-health",
+ "debug-history",
+ "debug-stats",
+ "debug-sync",
+ "debug-sync-url",
+ "debug-trends"
+ ],
+ "lines": 835
+ },
+ "module-fetcher": {
+ "file": "modules/core/module-fetcher.js",
+ "tier": "core-bundle",
+ "commands": [
+ "extract",
+ "fetch"
+ ],
+ "lines": 116
+ },
+ "module-fs": {
+ "file": "modules/core/module-fs.js",
+ "tier": "core-bundle",
+ "commands": [
+ "cat",
+ "cd",
+ "cp",
+ "download",
+ "du",
+ "echo",
+ "empty-trash",
+ "find",
+ "grep",
+ "head",
+ "ls",
+ "ls-trash",
+ "mkdir",
+ "mv",
+ "put",
+ "pwd",
+ "rm",
+ "rmdir",
+ "sed",
+ "session-delete",
+ "session-export",
+ "session-import",
+ "session-list",
+ "session-load",
+ "session-new",
+ "session-save",
+ "sort",
+ "tail",
+ "touch",
+ "trash",
+ "trash-dir",
+ "tree",
+ "upload",
+ "vfs-export",
+ "vfs-import",
+ "vfs-size",
+ "wc"
+ ],
+ "lines": 750
+ },
+ "module-leases": {
+ "file": "modules/core/module-leases.js",
+ "tier": "core-bundle",
+ "commands": [
+ "lease-create",
+ "lease-list",
+ "lease-renew",
+ "lease-revoke"
+ ],
+ "lines": 138
+ },
+ "module-memory-graph": {
+ "file": "modules/core/module-memory-graph.js",
+ "tier": "core-bundle",
+ "commands": [
+ "memgraph-agent",
+ "memgraph-decay-config",
+ "memgraph-export",
+ "memgraph-learn",
+ "memgraph-prune",
+ "memgraph-search",
+ "memgraph-session",
+ "memgraph-stats",
+ "memgraph-status"
+ ],
+ "lines": 663
+ },
+ "module-modmgr": {
+ "file": "modules/core/module-modmgr.js",
+ "tier": "core-bundle",
+ "commands": [
+ "listmods",
+ "loadmod",
+ "module-disable",
+ "module-enable",
+ "module-info",
+ "module-list",
+ "module-load",
+ "module-reload",
+ "module-reset",
+ "module-stats",
+ "module-unload",
+ "modules",
+ "modules-list",
+ "removemod"
+ ],
+ "lines": 393
+ },
+ "module-neural-vfs": {
+ "file": "modules/core/module-neural-vfs.js",
+ "tier": "core-bundle",
+ "commands": [
+ "nvfs-alias",
+ "nvfs-cancel",
+ "nvfs-config",
+ "nvfs-index",
+ "nvfs-index-graph",
+ "nvfs-resolve",
+ "nvfs-save",
+ "nvfs-status",
+ "nvfs-sync",
+ "nvfs-tree"
+ ],
+ "lines": 1108
+ },
+ "module-ollama": {
+ "file": "modules/core/module-ollama.js",
+ "tier": "core-bundle",
+ "commands": [
+ "ai",
+ "ask",
+ "model-delete",
+ "model-info",
+ "model-pull",
+ "model-run",
+ "models",
+ "ollama",
+ "ollamaconnect",
+ "ollamadisconnect",
+ "ollamamodels",
+ "ollamasetmodel",
+ "ollamasetproxy"
+ ],
+ "lines": 130
+ },
+ "module-orchestrator": {
+ "file": "modules/core/module-orchestrator.js",
+ "tier": "core-bundle",
+ "commands": [
+ "a2a-list",
+ "a2a-send",
+ "consensus",
+ "consensus-history",
+ "consensus-status",
+ "orchestrate",
+ "orchestrate-decompose",
+ "orchestrate-parallel",
+ "orchestrate-result",
+ "orchestrator-config",
+ "orchestrator-kill",
+ "orchestrator-list",
+ "orchestrator-purge",
+ "orchestrator-restart-policy",
+ "orchestrator-schedule",
+ "orchestrator-schedules",
+ "orchestrator-sleep",
+ "orchestrator-spawn",
+ "orchestrator-status",
+ "orchestrator-unschedule",
+ "orchestrator-wake",
+ "pipeline"
+ ],
+ "lines": 1380
+ },
+ "module-predictor": {
+ "file": "modules/core/module-predictor.js",
+ "tier": "core-bundle",
+ "commands": [
+ "predict",
+ "predictor-config",
+ "predictor-status"
+ ],
+ "lines": 282
+ },
+ "module-sandbox": {
+ "file": "modules/core/module-sandbox.js",
+ "tier": "core-bundle",
+ "commands": [
+ "sandbox-config",
+ "sandbox-run",
+ "sandbox-status"
+ ],
+ "lines": 372
+ },
+ "module-searxng": {
+ "file": "modules/core/module-searxng.js",
+ "tier": "core-bundle",
+ "commands": [
+ "images",
+ "s",
+ "search",
+ "videos"
+ ],
+ "lines": 116
+ },
+ "module-servermon": {
+ "file": "modules/core/module-servermon.js",
+ "tier": "core-bundle",
+ "commands": [
+ "monitor",
+ "server",
+ "server-check",
+ "server-remove",
+ "servers",
+ "system-health",
+ "test-net"
+ ],
+ "lines": 252
+ },
+ "module-simplegui": {
+ "file": "modules/core/module-simplegui.js",
+ "tier": "core-bundle",
+ "commands": [
+ "gui",
+ "gui-button",
+ "gui-clear",
+ "gui-input",
+ "gui-run",
+ "gui-text"
+ ],
+ "lines": 78
+ },
+ "module-smartctx": {
+ "file": "modules/core/module-smartctx.js",
+ "tier": "core-bundle",
+ "commands": [
+ "context",
+ "context-auto",
+ "context-delete",
+ "context-new",
+ "contexts",
+ "smart-ask"
+ ],
+ "lines": 285
+ },
+ "module-system": {
+ "file": "modules/core/module-system.js",
+ "tier": "core-bundle",
+ "commands": [
+ "bug-report",
+ "checkpoint",
+ "checkpoint-del",
+ "checkpoint-list",
+ "doctor",
+ "echo",
+ "errors",
+ "factory-reset",
+ "factory-reset-old",
+ "font-size",
+ "health-check",
+ "help",
+ "logrotate",
+ "logs",
+ "mem",
+ "memory-map-export",
+ "preview",
+ "reset",
+ "reset-vfs",
+ "rollback",
+ "setfont",
+ "status",
+ "uptime",
+ "version"
+ ],
+ "lines": 762
+ },
+ "module-world-model": {
+ "file": "modules/core/module-world-model.js",
+ "tier": "core-bundle",
+ "commands": [
+ "world-model",
+ "world-model-ask",
+ "world-model-config",
+ "world-model-refresh",
+ "world-model-summary"
+ ],
+ "lines": 502
+ },
+ "secret-vault": {
+ "file": "modules/core/secret-vault.js",
+ "tier": "core-bundle",
+ "commands": [
+ "secret-del",
+ "secret-get",
+ "secret-list",
+ "secret-lock",
+ "secret-set",
+ "secret-setpin",
+ "secret-status",
+ "secret-unlock"
+ ],
+ "lines": 442
+ },
+ "shell": {
+ "file": "modules/ext/shell.js",
+ "tier": "critical-ext-bundle",
+ "commands": [
+ "export",
+ "script",
+ "set",
+ "sh",
+ "unset"
+ ],
+ "lines": 627
+ },
+ "skill-pack-core": {
+ "file": "modules/ext/skill-pack-core.js",
+ "tier": "ext-auto",
+ "commands": [
+ "learn-skill",
+ "skill-audit",
+ "skill-create",
+ "skill-delete",
+ "skill-export",
+ "skill-import",
+ "skill-list",
+ "skill-pack-core",
+ "skill-patch",
+ "skill-restore",
+ "skill-sync",
+ "skill-validate",
+ "skill-view",
+ "skill-webdav-sync"
+ ],
+ "lines": 1150
+ },
+ "theme": {
+ "file": "modules/ext/theme.js",
+ "tier": "ext-auto",
+ "commands": [
+ "theme"
+ ],
+ "lines": 173
+ },
+ "three": {
+ "file": "modules/ext/three.js",
+ "tier": "ext-auto",
+ "commands": [
+ "three"
+ ],
+ "lines": 2426
+ },
+ "tutorial": {
+ "file": "modules/ext/tutorial.js",
+ "tier": "ext-auto",
+ "commands": [
+ "tutorial"
+ ],
+ "lines": 283
+ },
+ "voice": {
+ "file": "modules/ext/voice.js",
+ "tier": "ext-auto",
+ "commands": [
+ "speak",
+ "voice"
+ ],
+ "lines": 306
+ },
+ "webdav": {
+ "file": "modules/ext/webdav.js",
+ "tier": "ext-auto",
+ "commands": [
+ "webdav-backup",
+ "webdav-backup-list",
+ "webdav-browser",
+ "webdav-config",
+ "webdav-login",
+ "webdav-ls",
+ "webdav-mkdir",
+ "webdav-move",
+ "webdav-pull",
+ "webdav-push",
+ "webdav-rename",
+ "webdav-restore",
+ "webdav-rm",
+ "webdav-status"
+ ],
+ "lines": 2346
+ },
+ "wpm": {
+ "file": "modules/ext/wpm.js",
+ "tier": "ext-auto",
+ "commands": [
+ "wpm"
+ ],
+ "lines": 190
+ }
+ },
+ "coverage": {
+ "commands": 344,
+ "described": 344,
+ "modules": 46,
+ "tiers": {
+ "core-bundle": 257,
+ "critical-ext-bundle": 28,
+ "ext-auto": 59,
+ "ext-manual": 0
+ }
+ }
+} \ No newline at end of file
diff --git a/course/changelog.jsonl b/course/changelog.jsonl
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/course/changelog.jsonl
diff --git a/course/chapters/01-orientation.json b/course/chapters/01-orientation.json
new file mode 100644
index 0000000..165e140
--- /dev/null
+++ b/course/chapters/01-orientation.json
@@ -0,0 +1,132 @@
+{
+ "id": "01-orientation",
+ "title": "Orientation — what newOS4 is",
+ "icon": "🚀",
+ "level": "beginner",
+ "lessons": [
+ {
+ "id": "1.1",
+ "title": "A terminal in your browser",
+ "objective": "Understand what newOS4 is and run your first three commands.",
+ "minutes": 6,
+ "commands": [
+ "help",
+ "status",
+ "ls"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "newOS4 is a terminal-centric browser OS. Every command runs in your tab, files live in a virtual filesystem (VFS), and modules add commands at runtime."
+ },
+ {
+ "type": "cmd",
+ "text": "help"
+ },
+ {
+ "type": "text",
+ "text": "`help` lists every registered command grouped by the module that owns it."
+ },
+ {
+ "type": "cmd",
+ "text": "status"
+ },
+ {
+ "type": "cmd",
+ "text": "ls"
+ },
+ {
+ "type": "tip",
+ "text": "The VFS survives reloads. Create a file with `touch notes.txt`, then run `ls`."
+ }
+ ],
+ "exercise": {
+ "instruction": "Run `status` and read the loaded-modules line.",
+ "accept": [
+ "status"
+ ],
+ "check": {
+ "type": "command"
+ },
+ "hint": "Type: status"
+ },
+ "quiz": {
+ "q": "Where do files live in newOS4?",
+ "options": [
+ "On the web server",
+ "In the browser VFS",
+ "In a SQL database"
+ ],
+ "answer": 1
+ },
+ "sources": [
+ "modules/core/terminal.js",
+ "modules/core/virtual-fs.js"
+ ]
+ },
+ {
+ "id": "1.2",
+ "title": "Finding your way around",
+ "objective": "Navigate the virtual filesystem and create your first file.",
+ "minutes": 8,
+ "commands": [
+ "pwd",
+ "cd",
+ "tree",
+ "cat",
+ "touch"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "The VFS is a tree rooted at `/`. `pwd` prints where you are, `cd` moves, `tree` shows the branch, `cat` prints a file."
+ },
+ {
+ "type": "cmd",
+ "text": "pwd"
+ },
+ {
+ "type": "cmd",
+ "text": "tree"
+ },
+ {
+ "type": "warn",
+ "text": "`echo \"text\" > file` is NOT supported by the VFS. Use `touch`, the editor (`edit <file>`), or `webx11.fs.writeFile()` in the console."
+ },
+ {
+ "type": "cmd",
+ "text": "touch hello.txt"
+ },
+ {
+ "type": "cmd",
+ "text": "ls"
+ }
+ ],
+ "exercise": {
+ "instruction": "From the root, create `hello.txt` and list it.",
+ "accept": [
+ "touch hello.txt",
+ "ls"
+ ],
+ "check": {
+ "type": "file",
+ "path": "/hello.txt"
+ },
+ "hint": "Type: touch hello.txt — then ls"
+ },
+ "quiz": {
+ "q": "Which command prints the current directory?",
+ "options": [
+ "ls",
+ "pwd",
+ "cd"
+ ],
+ "answer": 1
+ },
+ "sources": [
+ "modules/core/module-fs.js",
+ "modules/core/virtual-fs.js"
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/course/chapters/02-architecture.json b/course/chapters/02-architecture.json
new file mode 100644
index 0000000..7dee513
--- /dev/null
+++ b/course/chapters/02-architecture.json
@@ -0,0 +1,131 @@
+{
+ "id": "02-architecture",
+ "title": "Architecture without the mystery",
+ "icon": "🏗",
+ "level": "beginner",
+ "lessons": [
+ {
+ "id": "2.1",
+ "title": "Terminal, modules, commands",
+ "objective": "See how the terminal, module manager and command registry fit together.",
+ "minutes": 9,
+ "commands": [
+ "modules",
+ "status",
+ "help"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "`terminal.js` parses input and dispatches a command to the module that registered it. Each module owns a `commands` map and contributes help strings."
+ },
+ {
+ "type": "cmd",
+ "text": "modules"
+ },
+ {
+ "type": "text",
+ "text": "The module manager lists every loaded module and its command count."
+ },
+ {
+ "type": "cmd",
+ "text": "status"
+ },
+ {
+ "type": "text",
+ "text": "`status` shows files, modules and network state in one place."
+ },
+ {
+ "type": "cmd",
+ "text": "check"
+ },
+ {
+ "type": "tip",
+ "text": "A command that exists in `help` but fails at runtime usually has an unmet module requirement that `check` reports."
+ }
+ ],
+ "exercise": {
+ "instruction": "List the loaded modules.",
+ "accept": [
+ "modules",
+ "status"
+ ],
+ "check": {
+ "type": "command"
+ },
+ "hint": "Type: modules"
+ },
+ "quiz": {
+ "q": "Who executes a command after you press Enter?",
+ "options": [
+ "nginx",
+ "the module that registered it",
+ "the browser cache"
+ ],
+ "answer": 1
+ },
+ "sources": [
+ "modules/core/terminal.js",
+ "modules/core/module-manager.js",
+ "modules/core/module-system.js"
+ ]
+ },
+ {
+ "id": "2.2",
+ "title": "VFS and the three loading tiers",
+ "objective": "Understand where files live and why a module edit sometimes needs a rebuild and sometimes does not.",
+ "minutes": 10,
+ "commands": [
+ "status",
+ "help",
+ "load-local"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "Files live in `virtual-fs.js` (in-memory + localStorage persistence). Directories are nodes; `readFile()` returns `null` for a missing path."
+ },
+ {
+ "type": "text",
+ "text": "Modules ship in three tiers:"
+ },
+ {
+ "type": "table",
+ "text": "core-bundle.js — core modules (requires rebuild) | critical-ext-bundle.js — editor, shell, alias, finder, filemgr, diff, busybox (requires rebuild) | individually loaded ext modules — cache-busted by LATEST_VER (no rebuild)"
+ },
+ {
+ "type": "cmd",
+ "text": "status"
+ },
+ {
+ "type": "warn",
+ "text": "Individually loaded modules are cached with `?v=<webx11_cache_ver>`. Without bumping `LATEST_VER`, users keep the old file."
+ }
+ ],
+ "exercise": {
+ "instruction": "Run `status` and find the VFS size line.",
+ "accept": [
+ "status"
+ ],
+ "check": {
+ "type": "command"
+ },
+ "hint": "Type: status"
+ },
+ "quiz": {
+ "q": "Which tier needs `rebuild-bundles.py` after an edit?",
+ "options": [
+ "core-bundle.js",
+ "webdav.js",
+ "course.js"
+ ],
+ "answer": 0
+ },
+ "sources": [
+ "modules/core/virtual-fs.js",
+ "modules/core/module-loader.js",
+ "scripts/rebuild-bundles.py"
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/course/chapters/03-commands.json b/course/chapters/03-commands.json
new file mode 100644
index 0000000..10807cb
--- /dev/null
+++ b/course/chapters/03-commands.json
@@ -0,0 +1,119 @@
+{
+ "id": "03-commands",
+ "title": "Command mastery",
+ "icon": "⌨",
+ "level": "beginner",
+ "lessons": [
+ {
+ "id": "3.1",
+ "title": "Find any command fast",
+ "objective": "Use help, commands and check to discover what exists before guessing.",
+ "minutes": 7,
+ "commands": [
+ "help",
+ "status"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "`help` groups all 344 commands by the module that owns them."
+ },
+ {
+ "type": "cmd",
+ "text": "help"
+ },
+ {
+ "type": "cmd",
+ "text": "help webdav-push"
+ },
+ {
+ "type": "tip",
+ "text": "`check` shows module requirements — the fastest way to explain a command that is listed but not working."
+ }
+ ],
+ "exercise": {
+ "instruction": "Open the help for `webdav-push`.",
+ "accept": [
+ "help webdav-push"
+ ],
+ "check": {
+ "type": "command"
+ },
+ "hint": "Type: help webdav-push"
+ },
+ "quiz": {
+ "q": "Which command shows usage for one command?",
+ "options": [
+ "help <cmd>",
+ "status",
+ "ls"
+ ],
+ "answer": 0
+ },
+ "sources": [
+ "modules/core/module-system.js",
+ "modules/core/module-manager.js"
+ ]
+ },
+ {
+ "id": "3.2",
+ "title": "Pipes, grep, sed, tail",
+ "objective": "Combine commands and understand the two semantics that bite: per-line sed and tail -f deltas.",
+ "minutes": 12,
+ "commands": [
+ "grep",
+ "sed",
+ "tail",
+ "sort",
+ "head"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "Pipes feed one command into the next. `grep` filters lines, `sed` substitutes text, `tail` prints the end of a file."
+ },
+ {
+ "type": "cmd",
+ "text": "ls | grep txt"
+ },
+ {
+ "type": "warn",
+ "text": "`sed s/x/y/` substitutes the first match per LINE, not per file. `sed -i` is flagged destructive and needs confirmation."
+ },
+ {
+ "type": "text",
+ "text": "`tail -f` is interactive and stops with Ctrl+C. AI-initiated commands get a bounded read instead."
+ },
+ {
+ "type": "cmd",
+ "text": "tail hello.txt"
+ }
+ ],
+ "exercise": {
+ "instruction": "Search the current listing for `txt`.",
+ "accept": [
+ "ls | grep txt",
+ "grep txt"
+ ],
+ "check": {
+ "type": "command"
+ },
+ "hint": "Type: ls | grep txt"
+ },
+ "quiz": {
+ "q": "`sed s/a/b/` replaces…",
+ "options": [
+ "every match in the file",
+ "the first match on each line",
+ "only the last line"
+ ],
+ "answer": 1
+ },
+ "sources": [
+ "modules/core/module-fs.js",
+ "modules/ext/shell.js",
+ "modules/core/terminal.js"
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/course/chapters/04-ai.json b/course/chapters/04-ai.json
new file mode 100644
index 0000000..3552c53
--- /dev/null
+++ b/course/chapters/04-ai.json
@@ -0,0 +1,128 @@
+{
+ "id": "04-ai",
+ "title": "The AI assistant",
+ "icon": "🤖",
+ "level": "intermediate",
+ "lessons": [
+ {
+ "id": "4.1",
+ "title": "Ask, and let it use tools",
+ "objective": "Use the AI assistant and understand the confirmation boundary.",
+ "minutes": 9,
+ "commands": [
+ "ai",
+ "ask",
+ "ai-config",
+ "ai-model"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "`ai` and `ask` open the tool-calling assistant. It can read files, run safe commands and search the web."
+ },
+ {
+ "type": "cmd",
+ "text": "ai what can you do?"
+ },
+ {
+ "type": "text",
+ "text": "Destructive actions (writes, `sed -i`, deletes) require confirmation. AI-initiated commands run with a loop guard so they cannot recurse."
+ },
+ {
+ "type": "cmd",
+ "text": "ai-model"
+ },
+ {
+ "type": "cmd",
+ "text": "ai-config"
+ }
+ ],
+ "exercise": {
+ "instruction": "Ask the AI a short question.",
+ "accept": [
+ "ai *",
+ "ask *"
+ ],
+ "check": {
+ "type": "command"
+ },
+ "hint": "Type: ai \"hello\""
+ },
+ "quiz": {
+ "q": "What happens when the AI wants a destructive command?",
+ "options": [
+ "runs silently",
+ "asks for confirmation",
+ "restarts the terminal"
+ ],
+ "answer": 1
+ },
+ "sources": [
+ "modules/core/module-aiassist.js",
+ "modules/core/ollama-manager.js"
+ ]
+ },
+ {
+ "id": "4.2",
+ "title": "Context, memory and skills",
+ "objective": "Inspect and clear context, then browse the skill library the AI retrieves from.",
+ "minutes": 8,
+ "commands": [
+ "ai-context",
+ "ai-clear",
+ "skill-list",
+ "skill-validate",
+ "learn-skill"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "The assistant keeps recent context. `ai-context` shows it; `ai-clear` resets it without touching your files."
+ },
+ {
+ "type": "cmd",
+ "text": "ai-context"
+ },
+ {
+ "type": "cmd",
+ "text": "skill-list"
+ },
+ {
+ "type": "text",
+ "text": "Skills are markdown entries in `/skills/` indexed by `/skills/index.json`; `load_skills` retrieves them by query."
+ },
+ {
+ "type": "cmd",
+ "text": "skill-validate course-01-orientation"
+ },
+ {
+ "type": "tip",
+ "text": "This course installs its chapters as `course-*` skills, so the AI can teach what you are reading."
+ }
+ ],
+ "exercise": {
+ "instruction": "List the installed skills.",
+ "accept": [
+ "skill-list"
+ ],
+ "check": {
+ "type": "command"
+ },
+ "hint": "Type: skill-list"
+ },
+ "quiz": {
+ "q": "Where does the skill index live?",
+ "options": [
+ "/skills/index.json",
+ "/etc/skills",
+ "localStorage only"
+ ],
+ "answer": 0
+ },
+ "sources": [
+ "modules/ext/skill-pack-core.js",
+ "modules/core/module-aiassist.js"
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/course/chapters/05-modules.json b/course/chapters/05-modules.json
new file mode 100644
index 0000000..4b68f42
--- /dev/null
+++ b/course/chapters/05-modules.json
@@ -0,0 +1,115 @@
+{
+ "id": "05-modules",
+ "title": "Modules & packages",
+ "icon": "🧩",
+ "level": "intermediate",
+ "lessons": [
+ {
+ "id": "5.1",
+ "title": "Browse and install modules",
+ "objective": "See what is loaded and install from the WebX11 package index.",
+ "minutes": 8,
+ "commands": [
+ "modules",
+ "wpm",
+ "load-local"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "`wpm` is the package manager for terminal modules: list the index, install a module from `packages.json` or a URL."
+ },
+ {
+ "type": "cmd",
+ "text": "wpm list"
+ },
+ {
+ "type": "cmd",
+ "text": "modules"
+ },
+ {
+ "type": "text",
+ "text": "Every ext module in the default build auto-loads. There is no separate “needs loadmod” tier."
+ },
+ {
+ "type": "cmd",
+ "text": "load-local modules/ext/calc.js"
+ }
+ ],
+ "exercise": {
+ "instruction": "List available packages.",
+ "accept": [
+ "wpm list"
+ ],
+ "check": {
+ "type": "command"
+ },
+ "hint": "Type: wpm list"
+ },
+ "quiz": {
+ "q": "What does wpm install?",
+ "options": [
+ "terminal modules",
+ "system packages",
+ "browser extensions"
+ ],
+ "answer": 0
+ },
+ "sources": [
+ "modules/ext/wpm.js",
+ "packages.json",
+ "modules/core/module-loader.js"
+ ]
+ },
+ {
+ "id": "5.2",
+ "title": "Module anatomy (a complete mini module)",
+ "objective": "Read the contract every module follows: class, commands map, help info, registration.",
+ "minutes": 12,
+ "commands": [
+ "load-local"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "A module is a class with `commands`, `registerCommands()`, `getHelpInfo()` and optionally `init(terminal)`. The module manager injects `terminal` before calling `registerCommands()`."
+ },
+ {
+ "type": "code",
+ "text": "class HelloModule {\n constructor(){ this.commands = {}; }\n getHelpInfo(){ return { hello: 'hello — print a greeting' }; }\n registerCommands(){ this.commands['hello'] = () => this.terminal.print('hello newOS4'); }\n}"
+ },
+ {
+ "type": "tip",
+ "text": "Tier-3 modules register at the end with `webx11.moduleManager.registerModule(new HelloModule(), url)`."
+ },
+ {
+ "type": "cmd",
+ "text": "modules"
+ }
+ ],
+ "exercise": {
+ "instruction": "Open the module list and find a module you have not used.",
+ "accept": [
+ "modules"
+ ],
+ "check": {
+ "type": "command"
+ },
+ "hint": "Type: modules"
+ },
+ "quiz": {
+ "q": "What does the module manager inject before registerCommands()?",
+ "options": [
+ "the terminal",
+ "the database",
+ "nothing"
+ ],
+ "answer": 0
+ },
+ "sources": [
+ "modules/core/module-manager.js",
+ "modules/ext/kanban.js"
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/course/chapters/06-files-cloud.json b/course/chapters/06-files-cloud.json
new file mode 100644
index 0000000..697a5f5
--- /dev/null
+++ b/course/chapters/06-files-cloud.json
@@ -0,0 +1,132 @@
+{
+ "id": "06-files-cloud",
+ "title": "Files, editor, WebDAV",
+ "icon": "☁",
+ "level": "intermediate",
+ "lessons": [
+ {
+ "id": "6.1",
+ "title": "Edit and compare files",
+ "objective": "Use the editor and diff tools on the VFS.",
+ "minutes": 9,
+ "commands": [
+ "edit",
+ "diff",
+ "find",
+ "files"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "`edit` opens the VFS editor. `diff` compares two files. `find` searches names; `filemgr` opens the visual browser."
+ },
+ {
+ "type": "cmd",
+ "text": "edit hello.txt"
+ },
+ {
+ "type": "cmd",
+ "text": "find hello"
+ },
+ {
+ "type": "cmd",
+ "text": "diff hello.txt hello.txt"
+ },
+ {
+ "type": "tip",
+ "text": "The editor calls the tutorial/file-saved hook, so exercise progress can be detected from a save."
+ },
+ {
+ "type": "cmd",
+ "text": "files"
+ }
+ ],
+ "exercise": {
+ "instruction": "Search for your file by name.",
+ "accept": [
+ "find hello",
+ "find hello.txt"
+ ],
+ "check": {
+ "type": "command"
+ },
+ "hint": "Type: find hello"
+ },
+ "quiz": {
+ "q": "Which command opens the visual file manager?",
+ "options": [
+ "filemgr",
+ "ls",
+ "cat"
+ ],
+ "answer": 0
+ },
+ "sources": [
+ "modules/ext/editor.js",
+ "modules/ext/diff.js",
+ "modules/ext/filemgr.js"
+ ]
+ },
+ {
+ "id": "6.2",
+ "title": "Cloud sync and login",
+ "objective": "Understand WebDAV auth, the space token, and the 401 self-heal path.",
+ "minutes": 14,
+ "commands": [
+ "webdav-login",
+ "webdav-config",
+ "webdav-status",
+ "webdav-push",
+ "webdav-backup"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "WebDAV syncs your VFS to your space. The module holds a SPACE token, not your personal token; it refreshes on 401."
+ },
+ {
+ "type": "cmd",
+ "text": "webdav-status"
+ },
+ {
+ "type": "cmd",
+ "text": "webdav-login"
+ },
+ {
+ "type": "warn",
+ "text": "Sessions do not cross browser contexts. If a push 401s, run `webdav-login` in THIS browser, then retry."
+ },
+ {
+ "type": "cmd",
+ "text": "webdav-push"
+ },
+ {
+ "type": "cmd",
+ "text": "webdav-backup"
+ }
+ ],
+ "exercise": {
+ "instruction": "Check the WebDAV status.",
+ "accept": [
+ "webdav-status"
+ ],
+ "check": {
+ "type": "command"
+ },
+ "hint": "Type: webdav-status"
+ },
+ "quiz": {
+ "q": "A 401 push usually means…",
+ "options": [
+ "the file is too big",
+ "no session/token in this browser context",
+ "WebDAV is disabled"
+ ],
+ "answer": 1
+ },
+ "sources": [
+ "modules/ext/webdav.js"
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/course/chapters/07-automation.json b/course/chapters/07-automation.json
new file mode 100644
index 0000000..3345fc2
--- /dev/null
+++ b/course/chapters/07-automation.json
@@ -0,0 +1,134 @@
+{
+ "id": "07-automation",
+ "title": "Automation",
+ "icon": "⚙",
+ "level": "advanced",
+ "lessons": [
+ {
+ "id": "7.1",
+ "title": "Schedule commands with cron",
+ "objective": "Run a command on an interval and manage the schedule.",
+ "minutes": 8,
+ "commands": [
+ "cron-every",
+ "cron-list",
+ "cron-stop",
+ "cron-clear"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "`cron-every` schedules a command inside the terminal (minimum 5 seconds). Jobs live only while the tab is open."
+ },
+ {
+ "type": "cmd",
+ "text": "cron-every 60 echo hello"
+ },
+ {
+ "type": "cmd",
+ "text": "cron-list"
+ },
+ {
+ "type": "cmd",
+ "text": "cron-stop 1"
+ },
+ {
+ "type": "cmd",
+ "text": "cron-clear"
+ },
+ {
+ "type": "tip",
+ "text": "For server-side schedules use Hermes cron; the terminal scheduler is per-tab."
+ }
+ ],
+ "exercise": {
+ "instruction": "List active cron jobs.",
+ "accept": [
+ "cron-list"
+ ],
+ "check": {
+ "type": "command"
+ },
+ "hint": "Type: cron-list"
+ },
+ "quiz": {
+ "q": "How long does a terminal cron job live?",
+ "options": [
+ "forever on the server",
+ "while the tab is open",
+ "until reboot"
+ ],
+ "answer": 1
+ },
+ "sources": [
+ "modules/ext/cron.js"
+ ]
+ },
+ {
+ "id": "7.2",
+ "title": "Kanban and checkpoints",
+ "objective": "Track work on a board and snapshot the VFS before risky changes.",
+ "minutes": 8,
+ "commands": [
+ "kanban",
+ "checkpoint",
+ "checkpoint-list",
+ "rollback"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "`kanban` is a local board with todo/doing/done columns. `checkpoint` snapshots the VFS; `rollback` restores it."
+ },
+ {
+ "type": "cmd",
+ "text": "kanban add \"Try the course\""
+ },
+ {
+ "type": "cmd",
+ "text": "kanban list"
+ },
+ {
+ "type": "cmd",
+ "text": "checkpoint before-project"
+ },
+ {
+ "type": "cmd",
+ "text": "checkpoint-list"
+ },
+ {
+ "type": "cmd",
+ "text": "rollback before-project"
+ },
+ {
+ "type": "warn",
+ "text": "`rollback` replaces current VFS state — checkpoint before experiments."
+ }
+ ],
+ "exercise": {
+ "instruction": "Create a checkpoint.",
+ "accept": [
+ "checkpoint *",
+ "checkpoint"
+ ],
+ "check": {
+ "type": "command"
+ },
+ "hint": "Type: checkpoint my-test"
+ },
+ "quiz": {
+ "q": "What does checkpoint do?",
+ "options": [
+ "snapshots the VFS",
+ "deletes files",
+ "pushes to WebDAV"
+ ],
+ "answer": 0
+ },
+ "sources": [
+ "modules/ext/kanban.js",
+ "modules/core/module-fs.js"
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/course/chapters/08-build.json b/course/chapters/08-build.json
new file mode 100644
index 0000000..48a60bd
--- /dev/null
+++ b/course/chapters/08-build.json
@@ -0,0 +1,117 @@
+{
+ "id": "08-build",
+ "title": "Build & extend",
+ "icon": "🔧",
+ "level": "advanced",
+ "lessons": [
+ {
+ "id": "8.1",
+ "title": "Add a command safely",
+ "objective": "Follow the edit → syntax → test → rebuild → version-bump loop.",
+ "minutes": 12,
+ "commands": [
+ "load-local",
+ "status",
+ "help"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "Edit the source, run `node --check`, run the relevant test harness, rebuild bundles for tiers 1–2, then bump BOTH `main.html` and `LATEST_VER`."
+ },
+ {
+ "type": "cmd",
+ "text": "status"
+ },
+ {
+ "type": "cmd",
+ "text": "help load-local"
+ },
+ {
+ "type": "warn",
+ "text": "Editing a tier-3 module without bumping `LATEST_VER` means users keep the stale cached file indefinitely."
+ }
+ ],
+ "exercise": {
+ "instruction": "Run the built-in test suite.",
+ "accept": [
+ "status"
+ ],
+ "check": {
+ "type": "command"
+ },
+ "hint": "Type: test auto"
+ },
+ "quiz": {
+ "q": "Which two places carry the version?",
+ "options": [
+ "main.html and LATEST_VER",
+ "package.json only",
+ "localStorage"
+ ],
+ "answer": 0
+ },
+ "sources": [
+ "scripts/rebuild-bundles.py",
+ "modules/core/module-loader.js"
+ ]
+ },
+ {
+ "id": "8.2",
+ "title": "Debug and observe",
+ "objective": "Use the debug panel, console and health checks when something breaks.",
+ "minutes": 8,
+ "commands": [
+ "debug",
+ "health-check",
+ "js"
+ ],
+ "blocks": [
+ {
+ "type": "text",
+ "text": "`debug` toggles the runtime debug panel; `console` opens a JS console in the terminal; `health-check` audits VFS, modules, logs and skills."
+ },
+ {
+ "type": "cmd",
+ "text": "health-check"
+ },
+ {
+ "type": "cmd",
+ "text": "debug"
+ },
+ {
+ "type": "tip",
+ "text": "Errors are posted to `/spaces/newos4/api/debug/ingest.php` and clustered in the debug dashboard."
+ },
+ {
+ "type": "cmd",
+ "text": "js 1+1"
+ }
+ ],
+ "exercise": {
+ "instruction": "Run a health check.",
+ "accept": [
+ "health-check"
+ ],
+ "check": {
+ "type": "command"
+ },
+ "hint": "Type: health-check"
+ },
+ "quiz": {
+ "q": "Where do runtime errors get ingested?",
+ "options": [
+ "/api/debug/ingest.php",
+ "/dev/null",
+ "localStorage"
+ ],
+ "answer": 0
+ },
+ "sources": [
+ "modules/ext/debug-panel.js",
+ "modules/core/module-debug-supervisor.js",
+ "api/debug/ingest.php"
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/course/course.json b/course/course.json
new file mode 100644
index 0000000..a355156
--- /dev/null
+++ b/course/course.json
@@ -0,0 +1,156 @@
+{
+ "version": "2026.10.18.1",
+ "updated": "2026-10-18",
+ "chapters": [
+ {
+ "id": "01-orientation",
+ "title": "Orientation — what newOS4 is",
+ "icon": "🚀",
+ "level": "beginner",
+ "lessons": [
+ {
+ "id": "1.1",
+ "title": "A terminal in your browser",
+ "minutes": 6
+ },
+ {
+ "id": "1.2",
+ "title": "Finding your way around",
+ "minutes": 8
+ }
+ ]
+ },
+ {
+ "id": "02-architecture",
+ "title": "Architecture without the mystery",
+ "icon": "🏗",
+ "level": "beginner",
+ "lessons": [
+ {
+ "id": "2.1",
+ "title": "Terminal, modules, commands",
+ "minutes": 9
+ },
+ {
+ "id": "2.2",
+ "title": "VFS and the three loading tiers",
+ "minutes": 10
+ }
+ ]
+ },
+ {
+ "id": "03-commands",
+ "title": "Command mastery",
+ "icon": "⌨",
+ "level": "beginner",
+ "lessons": [
+ {
+ "id": "3.1",
+ "title": "Find any command fast",
+ "minutes": 7
+ },
+ {
+ "id": "3.2",
+ "title": "Pipes, grep, sed, tail",
+ "minutes": 12
+ }
+ ]
+ },
+ {
+ "id": "04-ai",
+ "title": "The AI assistant",
+ "icon": "🤖",
+ "level": "intermediate",
+ "lessons": [
+ {
+ "id": "4.1",
+ "title": "Ask, and let it use tools",
+ "minutes": 9
+ },
+ {
+ "id": "4.2",
+ "title": "Context, memory and skills",
+ "minutes": 8
+ }
+ ]
+ },
+ {
+ "id": "05-modules",
+ "title": "Modules & packages",
+ "icon": "🧩",
+ "level": "intermediate",
+ "lessons": [
+ {
+ "id": "5.1",
+ "title": "Browse and install modules",
+ "minutes": 8
+ },
+ {
+ "id": "5.2",
+ "title": "Module anatomy (a complete mini module)",
+ "minutes": 12
+ }
+ ]
+ },
+ {
+ "id": "06-files-cloud",
+ "title": "Files, editor, WebDAV",
+ "icon": "☁",
+ "level": "intermediate",
+ "lessons": [
+ {
+ "id": "6.1",
+ "title": "Edit and compare files",
+ "minutes": 9
+ },
+ {
+ "id": "6.2",
+ "title": "Cloud sync and login",
+ "minutes": 14
+ }
+ ]
+ },
+ {
+ "id": "07-automation",
+ "title": "Automation",
+ "icon": "⚙",
+ "level": "advanced",
+ "lessons": [
+ {
+ "id": "7.1",
+ "title": "Schedule commands with cron",
+ "minutes": 8
+ },
+ {
+ "id": "7.2",
+ "title": "Kanban and checkpoints",
+ "minutes": 8
+ }
+ ]
+ },
+ {
+ "id": "08-build",
+ "title": "Build & extend",
+ "icon": "🔧",
+ "level": "advanced",
+ "lessons": [
+ {
+ "id": "8.1",
+ "title": "Add a command safely",
+ "minutes": 12
+ },
+ {
+ "id": "8.2",
+ "title": "Debug and observe",
+ "minutes": 8
+ }
+ ]
+ }
+ ],
+ "projects": [
+ "notes-bot",
+ "daily-digest",
+ "sync-backup"
+ ],
+ "feed": []
+} \ No newline at end of file
diff --git a/course/faq.json b/course/faq.json
new file mode 100644
index 0000000..fc69ce2
--- /dev/null
+++ b/course/faq.json
@@ -0,0 +1,3 @@
+{
+ "items": []
+} \ No newline at end of file
diff --git a/course/feed.json b/course/feed.json
new file mode 100644
index 0000000..81aac99
--- /dev/null
+++ b/course/feed.json
@@ -0,0 +1,4 @@
+{
+ "version": "2026-10-18",
+ "items": []
+} \ No newline at end of file
diff --git a/course/projects/daily-digest.json b/course/projects/daily-digest.json
new file mode 100644
index 0000000..e78ad46
--- /dev/null
+++ b/course/projects/daily-digest.json
@@ -0,0 +1,38 @@
+{
+ "id": "daily-digest",
+ "title": "Automated daily digest",
+ "level": "intermediate",
+ "minutes": 25,
+ "goal": "Schedule a repeating web fetch, save the result to a file, and inspect it.",
+ "lesson": "7.1",
+ "steps": [
+ {
+ "instruction": "Fetch a page and confirm output",
+ "command": "fetch https://example.com",
+ "check": {
+ "type": "command"
+ }
+ },
+ {
+ "instruction": "Schedule a 60s echo so you can see cron work",
+ "command": "cron-every 60 echo digest",
+ "check": {
+ "type": "command"
+ }
+ },
+ {
+ "instruction": "List the schedule",
+ "command": "cron-list",
+ "check": {
+ "type": "command"
+ }
+ },
+ {
+ "instruction": "Stop the job after one run",
+ "command": "cron-stop 1",
+ "check": {
+ "type": "command"
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/course/projects/notes-bot.json b/course/projects/notes-bot.json
new file mode 100644
index 0000000..53f8230
--- /dev/null
+++ b/course/projects/notes-bot.json
@@ -0,0 +1,39 @@
+{
+ "id": "notes-bot",
+ "title": "Personal notes with AI summaries",
+ "level": "beginner",
+ "minutes": 20,
+ "goal": "Create a notes folder, write a note, ask the AI to summarize it, and export it.",
+ "lesson": "1.2",
+ "steps": [
+ {
+ "instruction": "Create the notes folder",
+ "command": "mkdir notes",
+ "check": {
+ "type": "command"
+ }
+ },
+ {
+ "instruction": "Create a note file",
+ "command": "touch notes/today.txt",
+ "check": {
+ "type": "file",
+ "path": "/notes/today.txt"
+ }
+ },
+ {
+ "instruction": "Open it in the editor and write 3 lines",
+ "command": "edit notes/today.txt",
+ "check": {
+ "type": "manual"
+ }
+ },
+ {
+ "instruction": "Ask the AI to summarize the note",
+ "command": "ai summarize /notes/today.txt",
+ "check": {
+ "type": "manual"
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/course/projects/sync-backup.json b/course/projects/sync-backup.json
new file mode 100644
index 0000000..20c950d
--- /dev/null
+++ b/course/projects/sync-backup.json
@@ -0,0 +1,38 @@
+{
+ "id": "sync-backup",
+ "title": "Cloud backup with WebDAV",
+ "level": "intermediate",
+ "minutes": 30,
+ "goal": "Log in, check the space, push the VFS, and create a restorable backup.",
+ "lesson": "6.2",
+ "steps": [
+ {
+ "instruction": "Check the WebDAV state",
+ "command": "webdav-status",
+ "check": {
+ "type": "command"
+ }
+ },
+ {
+ "instruction": "Log in inside THIS browser if a push 401s",
+ "command": "webdav-login",
+ "check": {
+ "type": "manual"
+ }
+ },
+ {
+ "instruction": "Push the VFS",
+ "command": "webdav-push",
+ "check": {
+ "type": "manual"
+ }
+ },
+ {
+ "instruction": "Create a backup",
+ "command": "webdav-backup",
+ "check": {
+ "type": "command"
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/main.html b/main.html
index 035c329..0da0591 100644
--- a/main.html
+++ b/main.html
@@ -1234,6 +1234,10 @@
style="border-color:var(--success-color);color:var(--success-color);padding:12px 28px;font-size:1em;">
🎮 Start Tutorial
</button>
+ <button class="btn" onclick="var m=document.getElementById('welcome-modal');if(m)m.style.display='none';location.href='course.html'"
+ style="border-color:var(--info-color);color:var(--info-color);padding:12px 28px;font-size:1em;">
+ 📚 Full Course
+ </button>
<button class="btn" onclick="if(webx11)webx11.dismissWelcome()"
style="border-color:var(--text-muted);color:var(--text-muted);padding:12px 28px;font-size:1em;">
Skip — I'll explore
@@ -1308,7 +1312,7 @@
var mods = [
['Foundation (always loaded)', ['virtual-fs.js','module-manager.js','ollama-manager.js','terminal.js'], true],
['Built-in modules', ['module-system.js','module-fs.js','module-ollama.js','module-my.js','module-searxng.js','module-servermon.js','module-ollamamodels.js','module-config.js','module-simplegui.js','module-io.js','module-dynamicprompts.js','module-aiassist.js','module-aiasm.js','module-smartctx.js','module-duckduckgo.js','module-fetcher.js','module-chatstudio.js'], false],
- ['Extensions', ['calc.js','voice.js','cron.js','theme.js','wpm.js','mindmap.js','webdav.js','skill-pack-core.js','editor.js','diff.js','alias.js','finder.js','filemgr.js','cryptogzip.js','tutorial.js','shell.js','busybox.js','floating.js','console.js','kanban.js'], false]
+ ['Extensions', ['calc.js','voice.js','cron.js','theme.js','wpm.js','mindmap.js','webdav.js','skill-pack-core.js','editor.js','diff.js','alias.js','finder.js','filemgr.js','cryptogzip.js','tutorial.js','shell.js','busybox.js','floating.js','console.js','kanban.js','course.js'], false]
];
var isSel = function(name) { return true; }; // default: all selected
mods.forEach(function(section) {
@@ -1345,13 +1349,13 @@
</script>
<!-- v4.5.63: A2A scratchpad fixes + agent-register arg fix -->
-<script src="modules/core/core-bundle.js?v=v4.6.26"></script>
+<script src="modules/core/core-bundle.js?v=v4.6.27"></script>
<!-- Async dependency-ordered module loader now only initializes terminal and loads optional ext modules -->
-<script src="modules/core/module-loader.js?v=v4.6.26"></script>
+<script src="modules/core/module-loader.js?v=v4.6.27"></script>
<!-- Critical ext bundle loads AFTER webx11 exists so registerModule calls work -->
-<script src="modules/ext/critical-ext-bundle.js?v=v4.6.26"></script>
+<script src="modules/ext/critical-ext-bundle.js?v=v4.6.27"></script>
<script>
// ═══ ONE TRUE VERSION SOURCE ═══
diff --git a/modules-index.json b/modules-index.json
index ffa4efd..6d562f2 100644
--- a/modules-index.json
+++ b/modules-index.json
@@ -1,908 +1,1093 @@
{
- "version": "2026-06-30-v4.5.83",
- "generated": "2026-06-30T00:00:00.000Z",
- "modules": {
- "module-agent-registry": {
- "name": "module-agent-registry",
- "file": "modules/core/module-agent-registry.js",
- "type": "core",
- "class": "AgentRegistryModule",
- "auto_load": true,
- "commands": [
- "agent-list",
- "agent-status",
- "agent-register",
- "agent-unregister",
- "contract-register",
- "contract-list"
- ],
- "description": "module-agent-registry.js \u2014 VFS-backed agent directory with A2A contracts",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "core"
- },
- "module-aiasm": {
- "name": "module-aiasm",
- "file": "modules/core/module-aiasm.js",
- "type": "core",
- "class": "WebXAIAliasModule",
- "auto_load": true,
- "commands": [
- "webxai",
- "wx",
- "webx"
- ],
- "description": "Extracted from newOS4 index.html \u2014 WebXAIAliasModule",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "core"
- },
- "module-aiassist": {
- "name": "module-aiassist",
- "file": "modules/core/module-aiassist.js",
- "type": "core",
- "class": "WebX11AIAssistantModule",
- "auto_load": true,
- "commands": [
- "ai",
- "ask",
- "ai-clear",
- "ai-system",
- "ai-context",
- "ai-allow",
- "ai-deny",
- "ai-ignore",
- "ai-stop",
- "ai-model"
- ],
- "description": "Extracted from newOS4 index.html \u2014 WebX11AIAssistantModule (Full Agent Loop)",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "core"
- },
- "module-chatstudio": {
- "name": "module-chatstudio",
- "file": "modules/core/module-chatstudio.js",
- "type": "core",
- "class": "ChatStudioModule",
- "auto_load": true,
- "commands": [
- "chat"
- ],
- "description": "Load CSS",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "core"
- },
- "module-config": {
- "name": "module-config",
- "file": "modules/core/module-config.js",
- "type": "core",
- "class": "ConfigManagerModule",
- "auto_load": true,
- "commands": [
- "config",
- "config-set",
- "config-list",
- "config-delete",
- "config-export",
- "config-reset"
- ],
- "description": "Extracted from newOS4 index.html \u2014 ConfigManagerModule",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "core"
- },
- "module-fetcher": {
- "name": "module-fetcher",
- "file": "modules/core/module-fetcher.js",
- "type": "core",
- "class": "WebFetcherModule",
- "auto_load": true,
- "commands": [
- "fetch",
- "web",
- "extract"
- ],
- "description": "example.com\", \"info\");",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "core"
- },
- "module-fs": {
- "name": "module-fs",
- "file": "modules/core/module-fs.js",
- "type": "core",
- "class": "FileSystemModule",
- "auto_load": true,
- "commands": [
- "ls",
- "cat",
- "put",
- "rm",
- "trash",
- "trash-dir",
- "ls-trash",
- "empty-trash",
- "mkdir",
- "rmdir",
- "mv",
- "cp",
- "cd",
- "pwd",
- "find",
- "grep",
- "head",
- "tail",
- "sort",
- "wc",
- "touch",
- "tree",
- "du",
- "upload",
- "download",
- "vfs-export",
- "vfs-import"
- ],
- "description": "Use trash for recursive dir removal too",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "core"
- },
- "module-leases": {
- "name": "module-leases",
- "file": "modules/core/module-leases.js",
- "type": "core",
- "class": "LeaseModule",
- "auto_load": true,
- "commands": [
- "lease-list",
- "lease-create",
- "lease-revoke"
- ],
- "description": "module-leases.js \u2014 Lease system for agent task delegation",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "core"
- },
- "module-loader": {
- "name": "module-loader",
- "file": "modules/core/module-loader.js",
- "type": "core",
- "class": null,
- "auto_load": true,
- "commands": [],
- "description": "Async dependency-ordered module loader (foundation, not a module class)",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-08",
- "layer": "infrastructure"
- },
- "module-manager": {
- "name": "module-manager",
- "file": "modules/core/module-manager.js",
- "type": "core",
- "class": "ModuleManager",
- "auto_load": true,
- "commands": [
- "modules",
- "listmods",
- "loadmod",
- "removemod",
- "startup-modules",
- "load-local",
- "loadlocal"
- ],
- "description": "Extracted from newOS4 index.html \u2014 ModuleManager",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "infrastructure"
- },
- "module-modmgr": {
- "name": "module-modmgr",
- "file": "modules/core/module-modmgr.js",
- "type": "core",
- "class": "ModuleManagerModule",
- "auto_load": false,
- "commands": [
- "modules",
- "listmods",
- "loadmod",
- "removemod",
- "startup-modules",
- "load-local",
- "loadlocal"
- ],
- "description": "DEPRECATED 2026-06-08: duplicate of module-manager.js. Removed from BUILTIN. File kept for one release for rollback safety \u2014 will be deleted in v4.6.0.",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-08",
- "layer": "deprecated"
- },
- "module-neural-vfs": {
- "name": "module-neural-vfs",
- "file": "modules/core/module-neural-vfs.js",
- "type": "core",
- "class": "NeuralVFSModule",
- "auto_load": true,
- "commands": [
- "nvfs-index",
- "nvfs-status",
- "nvfs-resolve",
- "nvfs-tree"
- ],
- "description": "\u2500\u2500 Core Index Builder \u2500\u2500",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "core"
- },
- "module-ollama": {
- "name": "module-ollama",
- "file": "modules/core/module-ollama.js",
- "type": "core",
- "class": "OllamaModule",
- "auto_load": true,
- "commands": [
- "ollama",
- "ollamaconnect",
- "ollamadisconnect",
- "ollamamodels",
- "ollamasetmodel",
- "ollamasetproxy",
- "models",
- "model-info",
- "model-pull",
- "model-delete",
- "model-run",
- "ai",
- "ask"
- ],
- "description": "Ollama model management \u2014 connect, list models, pull/delete, run inference",
- "dependencies": [],
- "version": "2.0.0",
- "updated": "2026-06-04",
- "layer": "core"
- },
- "module-orchestrator": {
- "name": "module-orchestrator",
- "file": "modules/core/module-orchestrator.js",
- "type": "core",
- "class": "OrchestratorModule",
- "auto_load": true,
- "commands": [
- "orchestrate",
- "orchestrator-status",
- "orchestrator-spawn",
- "orchestrator-kill",
- "orchestrator-purge"
- ],
- "description": "module-orchestrator.js \u2014 Multi-agent orchestration system",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "core"
- },
- "module-searxng": {
- "name": "module-searxng",
- "file": "modules/core/module-searxng.js",
- "type": "core",
- "class": "SearXNGModule",
- "auto_load": true,
- "commands": [
- "search",
- "s",
- "images",
- "videos"
- ],
- "description": "",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "core"
- },
- "module-servermon": {
- "name": "module-servermon",
- "file": "modules/core/module-servermon.js",
- "type": "core",
- "class": "ServerMonitorModule",
- "auto_load": true,
- "commands": [
- "server",
- "servers",
- "server-check",
- "server-remove",
- "monitor",
- "system-health"
- ],
- "description": "module-servermon.js \u2014 Server monitoring + self-optimization telemetry",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "core"
- },
- "module-simplegui": {
- "name": "module-simplegui",
- "file": "modules/core/module-simplegui.js",
- "type": "core",
- "class": "SimpleGUIModule",
- "auto_load": true,
- "commands": [
- "gui",
- "gui-button",
- "gui-input",
- "gui-text",
- "gui-clear",
- "gui-run"
- ],
- "description": "Extracted from newOS4 index.html \u2014 SimpleGUIModule",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "core"
- },
- "module-system": {
- "name": "module-system",
- "file": "modules/core/module-system.js",
- "type": "core",
- "class": "SystemModule",
- "auto_load": true,
- "commands": [
- "help",
- "echo",
- "mem",
- "status",
- "preview",
- "factory-reset",
- "reset-vfs",
- "logs",
- "logrotate",
- "health-check",
- "memory-map-export",
- "font-size",
- "setfont",
- "smart-prompt",
- "checkpoint",
- "checkpoint-list",
- "rollback",
- "checkpoint-del",
- "commands",
- "check"
- ],
- "description": "Extracted from newOS4 index.html \u2014 SystemModule",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "core"
- },
- "ollama-manager": {
- "name": "ollama-manager",
- "file": "modules/core/ollama-manager.js",
- "type": "core",
- "class": "OllamaManager",
- "auto_load": true,
- "commands": [
- "ollamaconnect",
- "ollamamodels",
- "model",
- "models"
- ],
- "description": "Extracted from newOS4 index.html \u2014 OllamaManager / LLMRouter",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "infrastructure"
- },
- "secret-vault": {
- "name": "secret-vault",
- "file": "modules/core/secret-vault.js",
- "type": "core",
- "class": "SecretVault",
- "auto_load": true,
- "commands": [
- "secret-set",
- "secret-get",
- "secret-list",
- "secret-del",
- "secret-lock",
- "secret-unlock",
- "secret-setpin",
- "secret-status"
- ],
- "description": "Extracted from newOS4 index.html \u2014 SecretVault",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "core"
- },
- "terminal": {
- "name": "terminal",
- "file": "modules/core/terminal.js",
- "type": "core",
- "class": "WebX11SecurityManager",
- "auto_load": true,
- "commands": [
- "clear",
- "help",
- "security",
- "security-level",
- "cmd-allow",
- "cmd-deny",
- "cd"
- ],
- "description": "Extracted from newOS4 index.html \u2014 WebX11Terminal",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "infrastructure"
- },
- "virtual-fs": {
- "name": "virtual-fs",
- "file": "modules/core/virtual-fs.js",
- "type": "core",
- "class": "VirtualFileSystem",
- "auto_load": true,
- "commands": [
- "save",
- "load",
- "import"
- ],
- "description": "concept:// listing",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "infrastructure"
- },
- "alias": {
- "name": "alias",
- "file": "modules/ext/alias.js",
- "type": "ext",
- "class": "AliasModule",
- "auto_load": false,
- "commands": [
- "alias",
- "unalias"
- ],
- "description": "Register management commands",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "busybox": {
- "name": "busybox",
- "file": "modules/ext/busybox.js",
- "type": "ext",
- "class": "BusyBoxModule",
- "auto_load": false,
- "commands": [
- "which",
- "env",
- "date",
- "cal",
- "yes",
- "seq",
- "base64",
- "printf",
- "cut",
- "uniq",
- "hostname",
- "whoami",
- "basename",
- "dirname",
- "sleep",
- "expand"
- ],
- "description": "\u2500\u2500 which \u2500\u2500",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "calc": {
- "name": "calc",
- "file": "modules/ext/calc.js",
- "type": "ext",
- "class": "CalcModule",
- "auto_load": false,
- "commands": [
- "calc"
- ],
- "description": "webx11.duckdns.org/spaces/newos4/modules/ext/calc.js');",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "cron": {
- "name": "cron",
- "file": "modules/ext/cron.js",
- "type": "ext",
- "class": "CronModule",
- "auto_load": false,
- "commands": [
- "cron-every",
- "cron-list",
- "cron-stop",
- "cron-clear"
- ],
- "description": "webx11.duckdns.org/spaces/newos4/modules/ext/cron.js');",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "cryptogzip": {
- "name": "cryptogzip",
- "file": "modules/ext/cryptogzip.js",
- "type": "ext",
- "class": "CryptoGzipModule",
- "auto_load": false,
- "commands": [
- "gzip",
- "gunzip",
- "encrypt",
- "decrypt"
- ],
- "description": "CryptoGzipModule \u2014 VFS-level gzip/encrypt commands for WebX11 newOS4",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "diff": {
- "name": "diff",
- "file": "modules/ext/diff.js",
- "type": "ext",
- "class": "DiffModule",
- "auto_load": false,
- "commands": [
- "diff"
- ],
- "description": "Strip trailing empty line if file ends with newline",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "editor": {
- "name": "editor",
- "file": "modules/ext/editor.js",
- "type": "ext",
- "class": "EditorModule",
- "auto_load": false,
- "commands": [
- "edit"
- ],
- "description": "Read existing file content or use empty for new files",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "filemgr": {
- "name": "filemgr",
- "file": "modules/ext/filemgr.js",
- "type": "ext",
- "class": "FileManagerModule",
- "auto_load": false,
- "commands": [
- "files",
- "preview"
- ],
- "description": "code blocks first",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "finder": {
- "name": "finder",
- "file": "modules/ext/finder.js",
- "type": "ext",
- "class": "FinderModule",
- "auto_load": false,
- "commands": [
- "find"
- ],
- "description": "webx11.duckdns.org/spaces/newos4/modules/ext/finder.js');",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "mindmap": {
- "name": "mindmap",
- "file": "modules/ext/mindmap.js",
- "type": "ext",
- "class": "MindmapModule",
- "auto_load": false,
- "commands": [
- "mindmap",
- "svg"
- ],
- "description": "Interactive mindmap creation with inline SVG rendering",
- "dependencies": [],
- "version": "2.0.0",
- "updated": "2026-06-04",
- "layer": "ext"
- },
- "shell": {
- "name": "shell",
- "file": "modules/ext/shell.js",
- "type": "ext",
- "class": "ShellModule",
- "auto_load": false,
- "commands": [
- "export",
- "unset",
- "script",
- "$",
- "set"
- ],
- "description": "\u2500\u2500 Hook into terminal.executeCommand \u2500\u2500",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "skill-pack-core": {
- "name": "skill-pack-core",
- "file": "modules/ext/skill-pack-core.js",
- "type": "ext",
- "class": "SkillPackCoreModule",
- "auto_load": false,
- "commands": [
- "skill-pack-core"
- ],
- "description": "Update existing skill content on fresh load (cache-bust version bump triggers this)",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "theme": {
- "name": "theme",
- "file": "modules/ext/theme.js",
- "type": "ext",
- "class": "ThemeModule",
- "auto_load": false,
- "commands": [
- "theme"
- ],
- "description": "Cancel any active preview",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "tutorial": {
- "name": "tutorial",
- "file": "modules/ext/tutorial.js",
- "type": "ext",
- "class": "TutorialModule",
- "auto_load": false,
- "commands": [
- "tutorial"
- ],
- "description": "Restore saved state or start fresh",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "voice": {
- "name": "voice",
- "file": "modules/ext/voice.js",
- "type": "ext",
- "class": "VoiceModule",
- "auto_load": false,
- "commands": [
- "voice"
- ],
- "description": "webx11.duckdns.org/spaces/newos4/modules/ext/voice.js');",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "webdav": {
- "name": "webdav",
- "file": "modules/ext/webdav.js",
- "type": "ext",
- "class": "WebDAVModule",
- "auto_load": false,
- "commands": [
- "webdav-config",
- "webdav-status",
- "webdav-ls",
- "webdav-push",
- "webdav-pull",
- "webdav-backup",
- "webdav-restore",
- "webdav-backup-list",
- "webdav-browser",
- "webdav-rm",
- "webdav-mkdir",
- "webdav-rename",
- "webdav-move"
- ],
- "description": "{ url, username, password }",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "wpm": {
- "name": "wpm",
- "file": "modules/ext/wpm.js",
- "type": "ext",
- "class": "WpmModule",
- "auto_load": false,
- "commands": [
- "wpm"
- ],
- "description": "webx11.duckdns.org/spaces/newos4/packages.json';",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-03",
- "layer": "ext"
- },
- "test-suite": {
- "class": "TestSuiteModule",
- "file": "modules/test/test-suite.js",
- "layer": "ext",
- "description": "32 automated tests: filesystem, busybox, shell, cryptogzip, skills, security, modules. Load manually with load-local then test",
- "commands": [
- "test"
- ],
- "manual_load": true
- },
- "module-sandbox": {
- "class": "SandboxModule",
- "file": "modules/core/module-sandbox.js",
- "layer": "core",
- "description": "WebWorker sandbox pool for isolated task execution",
- "commands": [
- "sandbox-status",
- "sandbox-config",
- "sandbox-run"
- ]
- },
- "module-smartctx": {
- "class": "SmartContextModule",
- "file": "modules/core/module-smartctx.js",
- "layer": "core",
- "description": "AI context profiles — set AI behavior (coding, debug, creative, technical, chat)",
- "commands": [
- "context",
- "contexts",
- "context-new",
- "context-delete",
- "smart-ask"
- ]
- },
- "sandbox-worker": {
- "class": "SandboxWorker",
- "file": "modules/core/sandbox-worker.js",
- "layer": "infrastructure",
- "description": "WebWorker entry point for sandboxed agent contexts",
- "commands": []
- },
- "module-memory-graph": {
- "class": "MemoryGraphModule",
- "file": "modules/core/module-memory-graph.js",
- "layer": "core",
- "description": "Cross-session persistent memory graph \u2014 nodes for files, concepts, tasks, tools with weighted edges",
- "commands": [
- "memgraph-status",
- "memgraph-search",
- "memgraph-stats",
- "memgraph-export",
- "memgraph-prune"
- ]
- },
- "module-predictor": {
- "class": "PredictorModule",
- "file": "modules/core/module-predictor.js",
- "layer": "core",
- "description": "Predictive resource allocation \u2014 estimates task duration and tool turns from historical data",
- "commands": [
- "predict",
- "predictor-status",
- "predictor-config"
- ]
- },
- "module-world-model": {
- "class": "WorldModelModule",
- "file": "modules/core/module-world-model.js",
- "layer": "core",
- "description": "Global World Model \u2014 system self-awareness synthesis from all subsystems",
- "commands": [
- "world-model",
- "world-model-summary",
- "world-model-ask",
- "world-model-refresh",
- "world-model-config"
- ]
- },
- "media": {
- "name": "media",
- "file": "modules/ext/media.js",
- "type": "ext",
- "class": "MediaModule",
- "auto_load": true,
- "commands": [
- "analyze"
- ],
- "description": "media.js \u2014 ext module",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-18",
- "layer": "ext"
- },
- "console": {
- "name": "console",
- "file": "modules/ext/console.js",
- "type": "ext",
- "class": "JSConsoleModule",
- "auto_load": true,
- "commands": [
- "js",
- "js-run",
- "node"
- ],
- "description": "console.js \u2014 ext module",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-18",
- "layer": "ext"
- },
- "floating": {
- "name": "floating",
- "file": "modules/ext/floating.js",
- "type": "ext",
- "class": "FloatingModule",
- "auto_load": true,
- "commands": [
- "floating-add",
- "floating-remove",
- "floating-list",
- "floating-pos",
- "floating-clear",
- "floating-toggle"
- ],
- "description": "floating.js \u2014 ext module",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-18",
- "layer": "ext"
- },
- "debug-panel": {
- "name": "debug-panel",
- "file": "modules/ext/debug-panel.js",
- "type": "ext",
- "class": "DebugPanel",
- "auto_load": true,
- "commands": [
- "debug",
- "debug-ai"
- ],
- "description": "debug-panel.js \u2014 ext module",
- "dependencies": [],
- "version": "1.0.0",
- "updated": "2026-06-18",
- "layer": "ext"
- }
- },
- "total": 45
+ "version": "2026-10-18-atlas",
+ "generated": "2026-06-30T00:00:00.000Z",
+ "modules": {
+ "module-agent-registry": {
+ "name": "module-agent-registry",
+ "file": "modules/core/module-agent-registry.js",
+ "type": "core",
+ "class": "AgentRegistryModule",
+ "auto_load": true,
+ "commands": [
+ "a2a-ack",
+ "a2a-poll",
+ "a2a-post",
+ "a2a-prune",
+ "a2a-reject",
+ "a2a-status",
+ "agent-list",
+ "agent-register",
+ "agent-status",
+ "agent-unregister",
+ "contract-list",
+ "contract-register"
+ ],
+ "description": "module-agent-registry.js — VFS-backed agent directory with A2A contracts",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "module-aiasm": {
+ "name": "module-aiasm",
+ "file": "modules/core/module-aiasm.js",
+ "type": "core",
+ "class": "WebXAIAliasModule",
+ "auto_load": true,
+ "commands": [
+ "webx",
+ "webxai",
+ "wx"
+ ],
+ "description": "Extracted from newOS4 index.html — WebXAIAliasModule",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "module-aiassist": {
+ "name": "module-aiassist",
+ "file": "modules/core/module-aiassist.js",
+ "type": "core",
+ "class": "WebX11AIAssistantModule",
+ "auto_load": true,
+ "commands": [
+ "agent-loops",
+ "ai",
+ "ai-allow",
+ "ai-clear",
+ "ai-compress",
+ "ai-config",
+ "ai-context",
+ "ai-deny",
+ "ai-ignore",
+ "ai-model",
+ "ai-stop",
+ "ai-system",
+ "archive-list",
+ "archive-read",
+ "ask",
+ "bench",
+ "bench-compare",
+ "commit-approve",
+ "commit-deny",
+ "commit-status",
+ "debug",
+ "debug-ai",
+ "debug-ai-last",
+ "debug-ai-log",
+ "debug-ai-send",
+ "guardrail",
+ "guardrail-add",
+ "guardrail-remove",
+ "mem-debug",
+ "reset-mem",
+ "shadow",
+ "shadow-commit",
+ "shadow-rollback",
+ "vaultmind-connect",
+ "vaultmind-query",
+ "vaultmind-status",
+ "wallet-lookup"
+ ],
+ "description": "Extracted from newOS4 index.html — WebX11AIAssistantModule (Full Agent Loop)",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "module-chatstudio": {
+ "name": "module-chatstudio",
+ "file": "modules/core/module-chatstudio.js",
+ "type": "core",
+ "class": "ChatStudioModule",
+ "auto_load": true,
+ "commands": [
+ "chat"
+ ],
+ "description": "Load CSS",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "module-config": {
+ "name": "module-config",
+ "file": "modules/core/module-config.js",
+ "type": "core",
+ "class": "ConfigManagerModule",
+ "auto_load": true,
+ "commands": [
+ "config",
+ "config-delete",
+ "config-export",
+ "config-list",
+ "config-reset",
+ "config-set"
+ ],
+ "description": "Extracted from newOS4 index.html — ConfigManagerModule",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "module-fetcher": {
+ "name": "module-fetcher",
+ "file": "modules/core/module-fetcher.js",
+ "type": "core",
+ "class": "WebFetcherModule",
+ "auto_load": true,
+ "commands": [
+ "extract",
+ "fetch"
+ ],
+ "description": "example.com\", \"info\");",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "module-fs": {
+ "name": "module-fs",
+ "file": "modules/core/module-fs.js",
+ "type": "core",
+ "class": "FileSystemModule",
+ "auto_load": true,
+ "commands": [
+ "cat",
+ "cd",
+ "cp",
+ "download",
+ "du",
+ "echo",
+ "empty-trash",
+ "find",
+ "grep",
+ "head",
+ "ls",
+ "ls-trash",
+ "mkdir",
+ "mv",
+ "put",
+ "pwd",
+ "rm",
+ "rmdir",
+ "sed",
+ "session-delete",
+ "session-export",
+ "session-import",
+ "session-list",
+ "session-load",
+ "session-new",
+ "session-save",
+ "sort",
+ "tail",
+ "touch",
+ "trash",
+ "trash-dir",
+ "tree",
+ "upload",
+ "vfs-export",
+ "vfs-import",
+ "vfs-size",
+ "wc"
+ ],
+ "description": "Use trash for recursive dir removal too",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "module-leases": {
+ "name": "module-leases",
+ "file": "modules/core/module-leases.js",
+ "type": "core",
+ "class": "LeaseModule",
+ "auto_load": true,
+ "commands": [
+ "lease-create",
+ "lease-list",
+ "lease-renew",
+ "lease-revoke"
+ ],
+ "description": "module-leases.js — Lease system for agent task delegation",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "module-loader": {
+ "name": "module-loader",
+ "file": "modules/core/module-loader.js",
+ "type": "core",
+ "class": null,
+ "auto_load": true,
+ "commands": [],
+ "description": "Async dependency-ordered module loader (foundation, not a module class)",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-08",
+ "layer": "infrastructure"
+ },
+ "module-manager": {
+ "name": "module-manager",
+ "file": "modules/core/module-manager.js",
+ "type": "core",
+ "class": "ModuleManager",
+ "auto_load": true,
+ "commands": [
+ "modules",
+ "listmods",
+ "loadmod",
+ "removemod",
+ "startup-modules",
+ "load-local",
+ "loadlocal"
+ ],
+ "description": "Extracted from newOS4 index.html — ModuleManager",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "infrastructure"
+ },
+ "module-modmgr": {
+ "name": "module-modmgr",
+ "file": "modules/core/module-modmgr.js",
+ "type": "core",
+ "class": "ModuleManagerModule",
+ "auto_load": false,
+ "commands": [
+ "listmods",
+ "loadmod",
+ "module-disable",
+ "module-enable",
+ "module-info",
+ "module-list",
+ "module-load",
+ "module-reload",
+ "module-reset",
+ "module-stats",
+ "module-unload",
+ "modules",
+ "modules-list",
+ "removemod"
+ ],
+ "description": "DEPRECATED 2026-06-08: duplicate of module-manager.js. Removed from BUILTIN. File kept for one release for rollback safety — will be deleted in v4.6.0.",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-08",
+ "layer": "deprecated",
+ "tier": "core-bundle"
+ },
+ "module-neural-vfs": {
+ "name": "module-neural-vfs",
+ "file": "modules/core/module-neural-vfs.js",
+ "type": "core",
+ "class": "NeuralVFSModule",
+ "auto_load": true,
+ "commands": [
+ "nvfs-alias",
+ "nvfs-cancel",
+ "nvfs-config",
+ "nvfs-index",
+ "nvfs-index-graph",
+ "nvfs-resolve",
+ "nvfs-save",
+ "nvfs-status",
+ "nvfs-sync",
+ "nvfs-tree"
+ ],
+ "description": "── Core Index Builder ──",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "module-ollama": {
+ "name": "module-ollama",
+ "file": "modules/core/module-ollama.js",
+ "type": "core",
+ "class": "OllamaModule",
+ "auto_load": true,
+ "commands": [
+ "ai",
+ "ask",
+ "model-delete",
+ "model-info",
+ "model-pull",
+ "model-run",
+ "models",
+ "ollama",
+ "ollamaconnect",
+ "ollamadisconnect",
+ "ollamamodels",
+ "ollamasetmodel",
+ "ollamasetproxy"
+ ],
+ "description": "Ollama model management — connect, list models, pull/delete, run inference",
+ "dependencies": [],
+ "version": "2.0.0",
+ "updated": "2026-06-04",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "module-orchestrator": {
+ "name": "module-orchestrator",
+ "file": "modules/core/module-orchestrator.js",
+ "type": "core",
+ "class": "OrchestratorModule",
+ "auto_load": true,
+ "commands": [
+ "a2a-list",
+ "a2a-send",
+ "consensus",
+ "consensus-history",
+ "consensus-status",
+ "orchestrate",
+ "orchestrate-decompose",
+ "orchestrate-parallel",
+ "orchestrate-result",
+ "orchestrator-config",
+ "orchestrator-kill",
+ "orchestrator-list",
+ "orchestrator-purge",
+ "orchestrator-restart-policy",
+ "orchestrator-schedule",
+ "orchestrator-schedules",
+ "orchestrator-sleep",
+ "orchestrator-spawn",
+ "orchestrator-status",
+ "orchestrator-unschedule",
+ "orchestrator-wake",
+ "pipeline"
+ ],
+ "description": "module-orchestrator.js — Multi-agent orchestration system",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "module-searxng": {
+ "name": "module-searxng",
+ "file": "modules/core/module-searxng.js",
+ "type": "core",
+ "class": "SearXNGModule",
+ "auto_load": true,
+ "commands": [
+ "images",
+ "s",
+ "search",
+ "videos"
+ ],
+ "description": "",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "module-servermon": {
+ "name": "module-servermon",
+ "file": "modules/core/module-servermon.js",
+ "type": "core",
+ "class": "ServerMonitorModule",
+ "auto_load": true,
+ "commands": [
+ "monitor",
+ "server",
+ "server-check",
+ "server-remove",
+ "servers",
+ "system-health",
+ "test-net"
+ ],
+ "description": "module-servermon.js — Server monitoring + self-optimization telemetry",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "module-simplegui": {
+ "name": "module-simplegui",
+ "file": "modules/core/module-simplegui.js",
+ "type": "core",
+ "class": "SimpleGUIModule",
+ "auto_load": true,
+ "commands": [
+ "gui",
+ "gui-button",
+ "gui-clear",
+ "gui-input",
+ "gui-run",
+ "gui-text"
+ ],
+ "description": "Extracted from newOS4 index.html — SimpleGUIModule",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "module-system": {
+ "name": "module-system",
+ "file": "modules/core/module-system.js",
+ "type": "core",
+ "class": "SystemModule",
+ "auto_load": true,
+ "commands": [
+ "bug-report",
+ "checkpoint",
+ "checkpoint-del",
+ "checkpoint-list",
+ "doctor",
+ "echo",
+ "errors",
+ "factory-reset",
+ "factory-reset-old",
+ "font-size",
+ "health-check",
+ "help",
+ "logrotate",
+ "logs",
+ "mem",
+ "memory-map-export",
+ "preview",
+ "reset",
+ "reset-vfs",
+ "rollback",
+ "setfont",
+ "status",
+ "uptime",
+ "version"
+ ],
+ "description": "Extracted from newOS4 index.html — SystemModule",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "ollama-manager": {
+ "name": "ollama-manager",
+ "file": "modules/core/ollama-manager.js",
+ "type": "core",
+ "class": "OllamaManager",
+ "auto_load": true,
+ "commands": [
+ "ollamaconnect",
+ "ollamamodels",
+ "model",
+ "models"
+ ],
+ "description": "Extracted from newOS4 index.html — OllamaManager / LLMRouter",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "infrastructure"
+ },
+ "secret-vault": {
+ "name": "secret-vault",
+ "file": "modules/core/secret-vault.js",
+ "type": "core",
+ "class": "SecretVault",
+ "auto_load": true,
+ "commands": [
+ "secret-del",
+ "secret-get",
+ "secret-list",
+ "secret-lock",
+ "secret-set",
+ "secret-setpin",
+ "secret-status",
+ "secret-unlock"
+ ],
+ "description": "Extracted from newOS4 index.html — SecretVault",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "core",
+ "tier": "core-bundle"
+ },
+ "terminal": {
+ "name": "terminal",
+ "file": "modules/core/terminal.js",
+ "type": "core",
+ "class": "WebX11SecurityManager",
+ "auto_load": true,
+ "commands": [
+ "clear",
+ "help",
+ "security",
+ "security-level",
+ "cmd-allow",
+ "cmd-deny",
+ "cd"
+ ],
+ "description": "Extracted from newOS4 index.html — WebX11Terminal",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "infrastructure"
+ },
+ "virtual-fs": {
+ "name": "virtual-fs",
+ "file": "modules/core/virtual-fs.js",
+ "type": "core",
+ "class": "VirtualFileSystem",
+ "auto_load": true,
+ "commands": [
+ "save",
+ "load",
+ "import"
+ ],
+ "description": "concept:// listing",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "infrastructure"
+ },
+ "alias": {
+ "name": "alias",
+ "file": "modules/ext/alias.js",
+ "type": "ext",
+ "class": "AliasModule",
+ "auto_load": false,
+ "commands": [
+ "alias",
+ "unalias"
+ ],
+ "description": "Register management commands",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "critical-ext-bundle"
+ },
+ "busybox": {
+ "name": "busybox",
+ "file": "modules/ext/busybox.js",
+ "type": "ext",
+ "class": "BusyBoxModule",
+ "auto_load": false,
+ "commands": [
+ "base64",
+ "basename",
+ "cal",
+ "cut",
+ "date",
+ "dirname",
+ "env",
+ "expand",
+ "hostname",
+ "printf",
+ "seq",
+ "sleep",
+ "uniq",
+ "which",
+ "whoami",
+ "yes"
+ ],
+ "description": "── which ──",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "critical-ext-bundle"
+ },
+ "calc": {
+ "name": "calc",
+ "file": "modules/ext/calc.js",
+ "type": "ext",
+ "class": "CalcModule",
+ "auto_load": false,
+ "commands": [
+ "calc"
+ ],
+ "description": "webx11.duckdns.org/spaces/newos4/modules/ext/calc.js');",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "ext-auto"
+ },
+ "cron": {
+ "name": "cron",
+ "file": "modules/ext/cron.js",
+ "type": "ext",
+ "class": "CronModule",
+ "auto_load": false,
+ "commands": [
+ "cron-clear",
+ "cron-every",
+ "cron-list",
+ "cron-stop"
+ ],
+ "description": "webx11.duckdns.org/spaces/newos4/modules/ext/cron.js');",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "ext-auto"
+ },
+ "cryptogzip": {
+ "name": "cryptogzip",
+ "file": "modules/ext/cryptogzip.js",
+ "type": "ext",
+ "class": "CryptoGzipModule",
+ "auto_load": false,
+ "commands": [
+ "decrypt",
+ "encrypt",
+ "gunzip",
+ "gzip",
+ "tar",
+ "untar",
+ "wget"
+ ],
+ "description": "CryptoGzipModule — VFS-level gzip/encrypt commands for WebX11 newOS4",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "ext-auto"
+ },
+ "diff": {
+ "name": "diff",
+ "file": "modules/ext/diff.js",
+ "type": "ext",
+ "class": "DiffModule",
+ "auto_load": false,
+ "commands": [
+ "diff"
+ ],
+ "description": "Strip trailing empty line if file ends with newline",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "critical-ext-bundle"
+ },
+ "editor": {
+ "name": "editor",
+ "file": "modules/ext/editor.js",
+ "type": "ext",
+ "class": "EditorModule",
+ "auto_load": false,
+ "commands": [
+ "edit"
+ ],
+ "description": "Read existing file content or use empty for new files",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "critical-ext-bundle"
+ },
+ "filemgr": {
+ "name": "filemgr",
+ "file": "modules/ext/filemgr.js",
+ "type": "ext",
+ "class": "FileManagerModule",
+ "auto_load": false,
+ "commands": [
+ "files",
+ "preview"
+ ],
+ "description": "code blocks first",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "critical-ext-bundle"
+ },
+ "finder": {
+ "name": "finder",
+ "file": "modules/ext/finder.js",
+ "type": "ext",
+ "class": "FinderModule",
+ "auto_load": false,
+ "commands": [
+ "find"
+ ],
+ "description": "webx11.duckdns.org/spaces/newos4/modules/ext/finder.js');",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "critical-ext-bundle"
+ },
+ "mindmap": {
+ "name": "mindmap",
+ "file": "modules/ext/mindmap.js",
+ "type": "ext",
+ "class": "MindmapModule",
+ "auto_load": false,
+ "commands": [
+ "mindmap"
+ ],
+ "description": "Interactive mindmap creation with inline SVG rendering",
+ "dependencies": [],
+ "version": "2.0.0",
+ "updated": "2026-06-04",
+ "layer": "ext",
+ "tier": "ext-auto"
+ },
+ "shell": {
+ "name": "shell",
+ "file": "modules/ext/shell.js",
+ "type": "ext",
+ "class": "ShellModule",
+ "auto_load": false,
+ "commands": [
+ "export",
+ "script",
+ "set",
+ "sh",
+ "unset"
+ ],
+ "description": "── Hook into terminal.executeCommand ──",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "critical-ext-bundle"
+ },
+ "skill-pack-core": {
+ "name": "skill-pack-core",
+ "file": "modules/ext/skill-pack-core.js",
+ "type": "ext",
+ "class": "SkillPackCoreModule",
+ "auto_load": false,
+ "commands": [
+ "learn-skill",
+ "skill-audit",
+ "skill-create",
+ "skill-delete",
+ "skill-export",
+ "skill-import",
+ "skill-list",
+ "skill-pack-core",
+ "skill-patch",
+ "skill-restore",
+ "skill-sync",
+ "skill-validate",
+ "skill-view",
+ "skill-webdav-sync"
+ ],
+ "description": "Update existing skill content on fresh load (cache-bust version bump triggers this)",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "ext-auto"
+ },
+ "theme": {
+ "name": "theme",
+ "file": "modules/ext/theme.js",
+ "type": "ext",
+ "class": "ThemeModule",
+ "auto_load": false,
+ "commands": [
+ "theme"
+ ],
+ "description": "Cancel any active preview",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "ext-auto"
+ },
+ "tutorial": {
+ "name": "tutorial",
+ "file": "modules/ext/tutorial.js",
+ "type": "ext",
+ "class": "TutorialModule",
+ "auto_load": false,
+ "commands": [
+ "tutorial"
+ ],
+ "description": "Restore saved state or start fresh",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "ext-auto"
+ },
+ "voice": {
+ "name": "voice",
+ "file": "modules/ext/voice.js",
+ "type": "ext",
+ "class": "VoiceModule",
+ "auto_load": false,
+ "commands": [
+ "speak",
+ "voice"
+ ],
+ "description": "webx11.duckdns.org/spaces/newos4/modules/ext/voice.js');",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "ext-auto"
+ },
+ "webdav": {
+ "name": "webdav",
+ "file": "modules/ext/webdav.js",
+ "type": "ext",
+ "class": "WebDAVModule",
+ "auto_load": false,
+ "commands": [
+ "webdav-backup",
+ "webdav-backup-list",
+ "webdav-browser",
+ "webdav-config",
+ "webdav-login",
+ "webdav-ls",
+ "webdav-mkdir",
+ "webdav-move",
+ "webdav-pull",
+ "webdav-push",
+ "webdav-rename",
+ "webdav-restore",
+ "webdav-rm",
+ "webdav-status"
+ ],
+ "description": "{ url, username, password }",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "ext-auto"
+ },
+ "wpm": {
+ "name": "wpm",
+ "file": "modules/ext/wpm.js",
+ "type": "ext",
+ "class": "WpmModule",
+ "auto_load": false,
+ "commands": [
+ "wpm"
+ ],
+ "description": "webx11.duckdns.org/spaces/newos4/packages.json';",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-03",
+ "layer": "ext",
+ "tier": "ext-auto"
+ },
+ "test-suite": {
+ "class": "TestSuiteModule",
+ "file": "modules/test/test-suite.js",
+ "layer": "ext",
+ "description": "32 automated tests: filesystem, busybox, shell, cryptogzip, skills, security, modules. Load manually with load-local then test",
+ "commands": [
+ "test"
+ ],
+ "manual_load": true
+ },
+ "module-sandbox": {
+ "class": "SandboxModule",
+ "file": "modules/core/module-sandbox.js",
+ "layer": "core",
+ "description": "WebWorker sandbox pool for isolated task execution",
+ "commands": [
+ "sandbox-config",
+ "sandbox-run",
+ "sandbox-status"
+ ],
+ "tier": "core-bundle"
+ },
+ "module-smartctx": {
+ "class": "SmartContextModule",
+ "file": "modules/core/module-smartctx.js",
+ "layer": "core",
+ "description": "AI context profiles — set AI behavior (coding, debug, creative, technical, chat)",
+ "commands": [
+ "context",
+ "context-auto",
+ "context-delete",
+ "context-new",
+ "contexts",
+ "smart-ask"
+ ],
+ "tier": "core-bundle"
+ },
+ "sandbox-worker": {
+ "class": "SandboxWorker",
+ "file": "modules/core/sandbox-worker.js",
+ "layer": "infrastructure",
+ "description": "WebWorker entry point for sandboxed agent contexts",
+ "commands": []
+ },
+ "module-memory-graph": {
+ "class": "MemoryGraphModule",
+ "file": "modules/core/module-memory-graph.js",
+ "layer": "core",
+ "description": "Cross-session persistent memory graph — nodes for files, concepts, tasks, tools with weighted edges",
+ "commands": [
+ "memgraph-agent",
+ "memgraph-decay-config",
+ "memgraph-export",
+ "memgraph-learn",
+ "memgraph-prune",
+ "memgraph-search",
+ "memgraph-session",
+ "memgraph-stats",
+ "memgraph-status"
+ ],
+ "tier": "core-bundle"
+ },
+ "module-predictor": {
+ "class": "PredictorModule",
+ "file": "modules/core/module-predictor.js",
+ "layer": "core",
+ "description": "Predictive resource allocation — estimates task duration and tool turns from historical data",
+ "commands": [
+ "predict",
+ "predictor-config",
+ "predictor-status"
+ ],
+ "tier": "core-bundle"
+ },
+ "module-world-model": {
+ "class": "WorldModelModule",
+ "file": "modules/core/module-world-model.js",
+ "layer": "core",
+ "description": "Global World Model — system self-awareness synthesis from all subsystems",
+ "commands": [
+ "world-model",
+ "world-model-ask",
+ "world-model-config",
+ "world-model-refresh",
+ "world-model-summary"
+ ],
+ "tier": "core-bundle"
+ },
+ "media": {
+ "name": "media",
+ "file": "modules/ext/media.js",
+ "type": "ext",
+ "class": "MediaModule",
+ "auto_load": true,
+ "commands": [
+ "analyze"
+ ],
+ "description": "media.js — ext module",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-18",
+ "layer": "ext",
+ "tier": "ext-auto"
+ },
+ "console": {
+ "name": "console",
+ "file": "modules/ext/console.js",
+ "type": "ext",
+ "class": "JSConsoleModule",
+ "auto_load": true,
+ "commands": [
+ "js",
+ "js-run",
+ "node"
+ ],
+ "description": "console.js — ext module",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-18",
+ "layer": "ext",
+ "tier": "ext-auto"
+ },
+ "floating": {
+ "name": "floating",
+ "file": "modules/ext/floating.js",
+ "type": "ext",
+ "class": "FloatingModule",
+ "auto_load": true,
+ "commands": [
+ "floating-add",
+ "floating-clear",
+ "floating-list",
+ "floating-pos",
+ "floating-remove",
+ "floating-toggle"
+ ],
+ "description": "floating.js — ext module",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-18",
+ "layer": "ext",
+ "tier": "ext-auto"
+ },
+ "debug-panel": {
+ "name": "debug-panel",
+ "file": "modules/ext/debug-panel.js",
+ "type": "ext",
+ "class": "DebugPanel",
+ "auto_load": true,
+ "commands": [
+ "debug"
+ ],
+ "description": "debug-panel.js — ext module",
+ "dependencies": [],
+ "version": "1.0.0",
+ "updated": "2026-06-18",
+ "layer": "ext",
+ "tier": "ext-auto"
+ },
+ "kanban": {
+ "name": "kanban",
+ "file": "modules/ext/kanban.js",
+ "type": "ext",
+ "class": "",
+ "auto_load": true,
+ "description": "",
+ "commands": [
+ "kanban"
+ ],
+ "tier": "ext-auto"
+ },
+ "module-debug-supervisor": {
+ "name": "module-debug-supervisor",
+ "file": "modules/core/module-debug-supervisor.js",
+ "type": "core",
+ "class": "",
+ "auto_load": true,
+ "description": "",
+ "commands": [
+ "debug-analyze",
+ "debug-fix",
+ "debug-health",
+ "debug-history",
+ "debug-stats",
+ "debug-sync",
+ "debug-sync-url",
+ "debug-trends"
+ ],
+ "tier": "core-bundle"
+ },
+ "three": {
+ "name": "three",
+ "file": "modules/ext/three.js",
+ "type": "ext",
+ "class": "",
+ "auto_load": true,
+ "description": "",
+ "commands": [
+ "three"
+ ],
+ "tier": "ext-auto"
+ }
+ },
+ "total": 45
} \ No newline at end of file
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js
index dd86773..2714dd3 100644
--- a/modules/core/core-bundle.js
+++ b/modules/core/core-bundle.js
@@ -4283,9 +4283,17 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm
for (const modName in this.moduleManager.modules) {
const mod = this.moduleManager.modules[modName];
if (mod.commands && mod.commands[cmd]) {
+ if (cmd !== 'course') {
+ for (const _m of Object.values(this.moduleManager.modules)) {
+ if (typeof _m.onCommandStart === 'function') { try { _m.onCommandStart(cmd, args); } catch (_e) {} }
+ }
+ }
try { await mod.commands[cmd](args);
- // Notify tutorial module for quest tracking
- if (this.tutorialModule && cmd !== 'tutorial') this.tutorialModule.onCommandExecuted(cmd, args);
+ if (cmd !== 'tutorial' && cmd !== 'course') {
+ for (const _m of Object.values(this.moduleManager.modules)) {
+ if (typeof _m.onCommandExecuted === 'function') { try { _m.onCommandExecuted(cmd, args); } catch (_e) {} }
+ }
+ }
// Memory graph: record file access for read/file commands
if (cmd === 'cat' || cmd === 'view_module' || cmd === 'edit_module') {
try {
diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js
index 5e69094..53efb08 100644
--- a/modules/core/module-loader.js
+++ b/modules/core/module-loader.js
@@ -4,7 +4,7 @@
// Core module bundle loaded synchronously in main.html.
// This loader only handles version cache-busting, splash, terminal init, and optional ext modules.
- const LATEST_VER = 'v4.6.26';
+ const LATEST_VER = 'v4.6.27';
const CACHE = '?v=' + LATEST_VER;
const stored = localStorage.getItem('webx11_cache_ver');
if (stored !== LATEST_VER) {
@@ -53,7 +53,7 @@
setSplash('Initializing terminal (3/3)', total, total, '');
// webx11 already created synchronously above — just init ext modules
if (typeof webx11._loadExtModules === 'function') {
- let extKeys = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console', 'media', 'three', 'kanban'];
+ let extKeys = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console', 'media', 'three', 'kanban', 'course'];
const saved = localStorage.getItem('webx11_startup_modules');
let selected = null;
try { if (saved) selected = JSON.parse(saved); } catch (e) {}
diff --git a/modules/core/terminal.js b/modules/core/terminal.js
index 123c224..425c5d6 100644
--- a/modules/core/terminal.js
+++ b/modules/core/terminal.js
@@ -1857,9 +1857,17 @@
for (const modName in this.moduleManager.modules) {
const mod = this.moduleManager.modules[modName];
if (mod.commands && mod.commands[cmd]) {
+ if (cmd !== 'course') {
+ for (const _m of Object.values(this.moduleManager.modules)) {
+ if (typeof _m.onCommandStart === 'function') { try { _m.onCommandStart(cmd, args); } catch (_e) {} }
+ }
+ }
try { await mod.commands[cmd](args);
- // Notify tutorial module for quest tracking
- if (this.tutorialModule && cmd !== 'tutorial') this.tutorialModule.onCommandExecuted(cmd, args);
+ if (cmd !== 'tutorial' && cmd !== 'course') {
+ for (const _m of Object.values(this.moduleManager.modules)) {
+ if (typeof _m.onCommandExecuted === 'function') { try { _m.onCommandExecuted(cmd, args); } catch (_e) {} }
+ }
+ }
// Memory graph: record file access for read/file commands
if (cmd === 'cat' || cmd === 'view_module' || cmd === 'edit_module') {
try {
diff --git a/modules/ext/course.js b/modules/ext/course.js
new file mode 100644
index 0000000..3879cb5
--- /dev/null
+++ b/modules/ext/course.js
@@ -0,0 +1,323 @@
+class CourseModule {
+ constructor() {
+ this.commands = {};
+ this.terminal = null;
+ this.BASE = '/spaces/newos4/course/';
+ this.STATE_KEY = 'webx11_course_progress_v1';
+ this._manifest = null;
+ this._atlas = null;
+ this._chapters = {};
+ this._active = null;
+ this._capOut = '';
+ this._capActive = false;
+ }
+
+ getHelpInfo() {
+ return {
+ 'course': 'course [start|list|next|open <id>|check|hint|progress|reset|ask <q>|project [id]|cmds [q]] — Interactive newOS4 course'
+ };
+ }
+
+ registerCommands() {
+ this.commands['course'] = (args) => this.handle(args || []);
+ this._warm();
+ }
+
+ _v() {
+ const mod = localStorage.getItem('webx11_cache_ver') || '1';
+ return mod + '-' + new Date().toISOString().slice(0, 10);
+ }
+
+ async _warm() {
+ try { await this._load(); await this._installSkills(); } catch (e) { /* offline: commands report on use */ }
+ }
+
+ async _load(force) {
+ if (this._manifest && !force) return this._manifest;
+ const bust = this._v();
+ const [m, a] = await Promise.all([
+ fetch(this.BASE + 'course.json?v=' + bust).then(r => r.json()),
+ fetch(this.BASE + 'atlas.json?v=' + bust).then(r => r.json()).catch(() => null)
+ ]);
+ this._manifest = m;
+ this._atlas = a;
+ return m;
+ }
+
+ async _chapter(id) {
+ if (this._chapters[id]) return this._chapters[id];
+ const ch = await fetch(this.BASE + 'chapters/' + id + '.json?v=' + this._v()).then(r => r.json());
+ this._chapters[id] = ch;
+ return ch;
+ }
+
+ _progress() {
+ try { return JSON.parse(localStorage.getItem(this.STATE_KEY)) || { completed: {}, exercisePass: {}, xp: 0 }; }
+ catch (e) { return { completed: {}, exercisePass: {}, xp: 0 }; }
+ }
+
+ _save(p) { localStorage.setItem(this.STATE_KEY, JSON.stringify(p)); }
+
+ _printLesson(l, chTitle) {
+ const t = this.terminal;
+ t.print('┌─ ' + chTitle + ' › ' + l.title + ' ─', 'header');
+ t.print(' 🎯 ' + (l.objective || ''), 'info');
+ t.print(' ⏱ ' + (l.minutes || 5) + ' min · commands: ' + (l.commands || []).join(', '), 'text-muted');
+ t.print('');
+ for (const b of (l.blocks || [])) {
+ if (b.type === 'cmd') t.print(' $ ' + b.text, 'success');
+ else if (b.type === 'code') t.print(' ' + b.text, 'info');
+ else if (b.type === 'tip') t.print(' 💡 ' + b.text, 'warning');
+ else if (b.type === 'warn') t.print(' ⚠️ ' + b.text, 'error');
+ else t.print(' ' + b.text, '');
+ }
+ if (l.exercise) {
+ t.print('');
+ t.print(' 🏋️ Exercise: ' + l.exercise.instruction, 'info');
+ t.print(' Type the command(s); completion is detected automatically (or use `course check`).', 'text-muted');
+ }
+ if (l.quiz) {
+ t.print('');
+ t.print(' ❓ ' + l.quiz.q, 'info');
+ (l.quiz.options || []).forEach((o, i) => t.print(' ' + (i + 1) + ') ' + o, 'text-muted'));
+ t.print(' Answer with: `course quiz <number>`', 'text-muted');
+ }
+ }
+
+ async _open(lessonId) {
+ const m = await this._load();
+ for (const ch of m.chapters || []) {
+ const c = await this._chapter(ch.id);
+ const l = (c.lessons || []).find(x => x.id === lessonId);
+ if (!l) continue;
+ this._active = { lessonId, chapterId: ch.id, chapterTitle: ch.title, lesson: l };
+ this._printLesson(l, ch.title);
+ this._track('lesson_open', { lesson: lessonId });
+ return true;
+ }
+ this.terminal.print('Lesson not found: ' + lessonId, 'error');
+ return false;
+ }
+
+ _flat(m) {
+ return (m.chapters || []).map(ch => ({ id: ch.id, title: ch.title, icon: ch.icon || '📘', level: ch.level || 'beginner', lessons: ch.lessons || [] }));
+ }
+
+ async handle(args) {
+ const sub = (args[0] || 'start').toLowerCase();
+ let m;
+ try { m = await this._load(); }
+ catch (e) { return this.terminal.print('Course content unreachable (' + e.message + '). Try `course list` again.', 'error'); }
+ const p = this._progress();
+ if (sub === 'list' || sub === 'chapters') {
+ this.terminal.print('📚 newOS4 Course — v' + m.version + ' (' + m.updated + ')', 'header');
+ for (const ch of this._flat(m)) {
+ const done = ch.lessons.filter(l => p.completed[l.id]).length;
+ this.terminal.print(' ' + ch.icon + ' ' + ch.id + ' · ' + ch.title + ' [' + done + '/' + ch.lessons.length + ']', done ? 'success' : '');
+ }
+ this.terminal.print(' course start | open <lessonId> | next | progress', 'text-muted');
+ return;
+ }
+ if (sub === 'start' || sub === 'next') {
+ for (const ch of this._flat(m)) for (const l of ch.lessons) if (!p.completed[l.id]) return this._open(l.id);
+ return this.terminal.print('🎉 Course complete! Try `course project` or `course cmds <query>`.', 'success');
+ }
+ if (sub === 'open') { if (!args[1]) return this.terminal.print('Usage: course open <lessonId>', 'warning'); return this._open(args[1]); }
+ if (sub === 'hint') {
+ if (!this._active) return this.terminal.print('Open a lesson first: `course start`.', 'warning');
+ return this.terminal.print('💡 ' + (this._active.lesson.exercise?.hint || 'No hint for this lesson.'), 'warning');
+ }
+ if (sub === 'progress') {
+ const total = this._flat(m).reduce((n, ch) => n + ch.lessons.length, 0);
+ const done = Object.keys(p.completed).length;
+ this.terminal.print('📈 Progress: ' + done + '/' + total + ' lessons · ' + (p.xp || 0) + ' XP', 'header');
+ const next = this._flat(m).flatMap(c => c.lessons).find(l => !p.completed[l.id]);
+ if (next) this.terminal.print(' Next: `course open ' + next.id + '` — ' + next.title, 'info');
+ return;
+ }
+ if (sub === 'reset') {
+ if (args[1] !== '--confirm') return this.terminal.print('This erases course progress. Run: course reset --confirm', 'warning');
+ localStorage.removeItem(this.STATE_KEY); this._active = null; return this.terminal.print('Course progress reset.', 'success');
+ }
+ if (sub === 'quiz') {
+ if (!this._active || !this._active.lesson.quiz) return this.terminal.print('No quiz open.', 'warning');
+ const pick = parseInt(args[1], 10) - 1;
+ const ok = pick === this._active.lesson.quiz.answer;
+ if (ok) this._complete(this._active.lessonId, 5);
+ return this.terminal.print(ok ? '✅ Correct (+5 XP)' : '❌ Not quite — re-read with `course open ' + this._active.lessonId + '`.', ok ? 'success' : 'error');
+ }
+ if (sub === 'check') return this._manualCheck();
+ if (sub === 'cmds') return this._cmdSearch(args.slice(1).join(' '));
+ if (sub === 'project') return this._project(args[1]);
+ if (sub === 'ask') return this._ask(args.slice(1).join(' '));
+ this.terminal.print('Usage: ' + this.getHelpInfo().course, 'warning');
+ }
+
+ _complete(lessonId, xp) {
+ const p = this._progress();
+ if (!p.completed[lessonId]) {
+ p.completed[lessonId] = new Date().toISOString();
+ p.xp = (p.xp || 0) + (xp || 10);
+ this._save(p);
+ this.terminal.print('✅ Lesson ' + lessonId + ' complete (+' + (xp || 10) + ' XP)', 'success');
+ this._track('lesson_complete', { lesson: lessonId });
+ }
+ }
+
+ _cmdSearch(q) {
+ if (!this._atlas) return this.terminal.print('Atlas not loaded yet — retry in a moment.', 'warning');
+ const items = Object.entries(this._atlas.commands);
+ const hit = q ? items.filter(([c, e]) => (c + ' ' + e.desc + ' ' + e.module + ' ' + e.category).toLowerCase().includes(q.toLowerCase())) : items;
+ this.terminal.print('🔎 ' + (q ? hit.length + ' match(es)' : items.length + ' commands') + ' — `course cmds <query>` to filter', 'header');
+ for (const [c, e] of hit.slice(0, 40)) this.terminal.print(' ' + c.padEnd(18) + ' ' + (e.desc || e.module).slice(0, 90), 'text-muted');
+ }
+
+ onCommandStart(cmd, args) {
+ if (cmd === 'course') return;
+ if (this._capActive) { try { this.terminal._popPrintCapture(); } catch (e) {} this._capActive = false; }
+ this._capOut = '';
+ this._capActive = true;
+ try { this.terminal._pushPrintCapture(txt => { this._capOut = txt; }); } catch (e) { this._capActive = false; }
+ }
+
+ onCommandExecuted(cmd, args) {
+ if (this._capActive) { try { this.terminal._popPrintCapture(); } catch (e) {} this._capActive = false; }
+ if (!this._active || !this._active.lesson.exercise || cmd === 'course') return;
+ const ex = this._active.lesson.exercise;
+ const line = (cmd + (args.length ? ' ' + args.join(' ') : '')).trim();
+ if (!ex.accept.some(pat => this._match(pat, line))) return;
+ this._verify(ex, line);
+ }
+
+ _match(pattern, line) {
+ const esc = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
+ return new RegExp('^' + esc + '$', 'i').test(line);
+ }
+
+ _verify(ex, line) {
+ const check = ex.check || { type: 'command' };
+ let ok = false, detail = '';
+ if (check.type === 'command' || check.type === 'manual') ok = true;
+ else if (check.type === 'file') {
+ if (!check.path) { detail = 'exercise misconfigured: check.path missing'; }
+ else {
+ let exists = false;
+ try { const c = this.terminal.fs.readFile(check.path); exists = (c !== null && c !== undefined); } catch (e) { exists = false; }
+ ok = exists; detail = ok ? '' : 'file not found: ' + check.path;
+ }
+ } else if (check.type === 'output') {
+ ok = (this._capOut || '').toLowerCase().includes((check.contains || '').toLowerCase());
+ detail = ok ? '' : 'expected output to contain "' + check.contains + '"';
+ }
+ if (ok) this._complete(this._active.lessonId, 10);
+ else { this.terminal.print(' ✗ Not done yet — ' + (detail || 'keep trying'), 'warning'); this._track('exercise_fail', { lesson: this._active.lessonId, command: line.slice(0, 120) }); }
+ }
+
+ _manualCheck() {
+ if (!this._active) return this.terminal.print('Open a lesson first: `course start`.', 'warning');
+ const ex = this._active.lesson.exercise;
+ if (!ex) return this.terminal.print('This lesson has no exercise.', 'warning');
+ this._verify(ex, '(manual check)');
+ }
+
+ async _ask(q) {
+ if (!q) return this.terminal.print('Usage: course ask <question>', 'warning');
+ const l = this._active && this._active.lesson;
+ const sys = 'You are the newOS4 course tutor. Answer in <=120 words. Use ONLY the course context and command atlas below. '
+ + 'If something is not covered, say so and point to `help <cmd>` or the relevant lesson — never invent commands.\n\n'
+ + (l ? this._lessonContext(l) : this._atlasContext(q));
+ const model = (this.terminal.ollamaManager && this.terminal.ollamaManager.currentModel) || 'gemma4:31b-cloud';
+ const tok = localStorage.getItem('webx11_session_id') || '';
+ const headers = { 'Content-Type': 'application/json' };
+ if (tok) headers.Authorization = ['Bearer', tok].join(' ');
+ this.terminal.print('🤖 thinking…', 'text-muted');
+ try {
+ const r = await fetch('/ai/v1/chat/completions', {
+ method: 'POST', headers,
+ body: JSON.stringify({ model, stream: false, messages: [{ role: 'system', content: sys }, { role: 'user', content: q }] })
+ });
+ if (!r.ok) throw new Error('HTTP ' + r.status + (r.status === 401 ? ' — log in to use the AI tutor' : ''));
+ const d = await r.json();
+ const ans = (d.choices && d.choices[0] && d.choices[0].message && d.choices[0].message.content) || '(empty answer)';
+ this.terminal.print('🎓 ' + ans.trim(), 'info');
+ this._track('question', { q: q.slice(0, 240), lesson: l ? l.id : null });
+ } catch (e) {
+ this.terminal.print('AI tutor unavailable: ' + e.message + '. Use `ai <question>` as a fallback.', 'warning');
+ }
+ }
+
+ _lessonContext(l) {
+ const lines = ['LESSON: ' + l.title, 'OBJECTIVE: ' + l.objective, 'COMMANDS: ' + (l.commands || []).join(', ')];
+ for (const b of (l.blocks || [])) lines.push(b.type.toUpperCase() + ': ' + b.text);
+ if (l.exercise) lines.push('EXERCISE: ' + l.exercise.instruction + ' | HINT: ' + l.exercise.hint);
+ if (this._atlas) for (const c of (l.commands || [])) { const e = this._atlas.commands[c]; if (e) lines.push('ATLAS ' + c + ': ' + e.desc + ' [' + e.tier + ']'); }
+ return lines.join('\n').slice(0, 6000);
+ }
+
+ _atlasContext(q) {
+ if (!this._atlas) return 'No atlas loaded.';
+ const hits = Object.entries(this._atlas.commands)
+ .filter(([c, e]) => (c + ' ' + e.desc).toLowerCase().includes((q || '').toLowerCase().split(/\s+/)[0] || ''))
+ .slice(0, 8).map(([c, e]) => c + ': ' + e.desc);
+ return 'COMMAND ATLAS (top matches):\n' + hits.join('\n');
+ }
+
+ async _project(id) {
+ const m = await this._load();
+ const ids = m.projects || [];
+ if (!id) return this.terminal.print('🛠 Projects: ' + (ids.join(', ') || 'none yet') + ' — `course project <id>`', 'info');
+ const pr = await fetch(this.BASE + 'projects/' + id + '.json?v=' + this._v()).then(r => r.json());
+ this.terminal.print('🛠 ' + pr.title + ' (' + pr.minutes + ' min) — ' + pr.goal, 'header');
+ (pr.steps || []).forEach((s, i) => this.terminal.print(' ' + (i + 1) + '. ' + s.instruction + (s.command ? ' → ' + s.command : ''), ''));
+ this._track('project_open', { project: id });
+ }
+
+ async _installSkills() {
+ const fs = this.terminal && this.terminal.fs;
+ const m = this._manifest;
+ if (!fs || !m) return;
+ const key = 'webx11_course_skills_ver';
+ if (localStorage.getItem(key) === m.version) return;
+ try {
+ fs.mkdirRecursive('/skills');
+ const index = fs.fsReadJSON('/skills/index.json') || {};
+ let n = 0;
+ for (const ch of m.chapters || []) {
+ const c = this._chapters[ch.id] || await this._chapter(ch.id);
+ const body = (c.lessons || []).map(l => '## ' + l.title + '\n' + (l.blocks || [])
+ .filter(b => b.type === 'text' || b.type === 'warn' || b.type === 'tip')
+ .map(b => b.text).join('\n')).join('\n');
+ const name = 'course-' + ch.id.replace(/[^a-z0-9-]/gi, '-').toLowerCase();
+ const content = '---\nname: ' + name + '\ndescription: newOS4 course — ' + ch.title
+ + '\ntags: [course, newos4]\ncategory: learning\n---\n' + body;
+ fs.writeFile('/skills/' + name + '.md', content);
+ index[name] = {
+ name, description: 'newOS4 course — ' + ch.title,
+ tags: ['course', 'newos4'], category: 'learning',
+ created: (index[name] && index[name].created) || new Date().toISOString(),
+ updated: new Date().toISOString()
+ };
+ n++;
+ }
+ fs.fsWriteJSON('/skills/index.json', index);
+ localStorage.setItem(key, m.version);
+ if (n) this.terminal.print('📚 Course skills synced (' + n + ') — the AI can use them via `load_skills`.', 'success');
+ } catch (e) { /* VFS busy — retried on next load */ }
+ }
+
+ _track(type, detail) {
+ try {
+ const sid = localStorage.getItem('webx11_course_sid') || (Math.random().toString(36).slice(2) + Date.now().toString(36));
+ localStorage.setItem('webx11_course_sid', sid);
+ if (localStorage.getItem('webx11_course_telemetry') === 'off') return;
+ fetch('/spaces/newos4/api/course/event.php', {
+ method: 'POST', headers: { 'Content-Type': 'application/json' }, keepalive: true,
+ body: JSON.stringify({ sid, type, detail, version: (this._manifest && this._manifest.version) || '' })
+ }).catch(() => {});
+ } catch (e) {}
+ }
+}
+
+if (typeof window.webx11 !== 'undefined' && window.webx11) {
+ webx11.moduleManager.registerModule(new CourseModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/course.js');
+}
diff --git a/packages.json b/packages.json
index c3488ed..035b0b0 100644
--- a/packages.json
+++ b/packages.json
@@ -40,7 +40,7 @@
"theme": {
"name": "Theme Switcher",
"version": "1.0.0",
- "desc": "CSS theme manager \u2014 dark, light, amber, cyber + custom",
+ "desc": "CSS theme manager — dark, light, amber, cyber + custom",
"url": "modules/ext/theme.js",
"commands": [
"theme"
@@ -60,7 +60,7 @@
"webdav": {
"name": "WebDAV Sync",
"version": "1.0.0",
- "desc": "Cloud sync for VFS \u2014 backup, push, pull, and restore via WebDAV",
+ "desc": "Cloud sync for VFS — backup, push, pull, and restore via WebDAV",
"url": "modules/ext/webdav.js",
"commands": [
"webdav-config",
@@ -77,7 +77,7 @@
"mindmap": {
"name": "Mindmap",
"version": "1.0.0",
- "desc": "Brainstorming mindmap tool \u2014 new, add, show, export, save, load, color, tag",
+ "desc": "Brainstorming mindmap tool — new, add, show, export, save, load, color, tag",
"url": "modules/ext/mindmap.js",
"commands": [
"mindmap"
@@ -97,7 +97,7 @@
"editor": {
"name": "Text Editor",
"version": "1.0.0",
- "desc": "Inline text editor for VFS files \u2014 edit, save, cancel via modal",
+ "desc": "Inline text editor for VFS files — edit, save, cancel via modal",
"url": "modules/ext/editor.js",
"commands": [
"edit"
@@ -203,7 +203,7 @@
"floating": {
"name": "Floating UI",
"version": "1.0.0",
- "desc": "Remixable floating UI elements \u2014 buttons, collapsible menus, panels. Persisted in localStorage.",
+ "desc": "Remixable floating UI elements — buttons, collapsible menus, panels. Persisted in localStorage.",
"url": "modules/ext/floating.js",
"commands": [
"floating-add",
@@ -229,7 +229,7 @@
"debug-panel": {
"name": "Debug Panel",
"version": "1.0.0",
- "desc": "Toggleable debug console \u2014 intercepts console.log/error, fetch failures, JS errors. Copy All + per-line copy.",
+ "desc": "Toggleable debug console — intercepts console.log/error, fetch failures, JS errors. Copy All + per-line copy.",
"url": "modules/ext/debug-panel.js",
"commands": [
"debug"
@@ -239,7 +239,7 @@
"tutorial": {
"name": "Tutorial",
"version": "1.0.0",
- "desc": "Interactive guided tour \u2014 6 quests to learn newOS4 basics",
+ "desc": "Interactive guided tour — 6 quests to learn newOS4 basics",
"url": "modules/ext/tutorial.js",
"commands": [
"tutorial"
@@ -259,7 +259,7 @@
"neural-vfs-core": {
"name": "Neural VFS (core)",
"version": "1.0.0",
- "desc": "Core semantic indexer. NOT a WPM package \u2014 built into newos4.tar.gz. Use run_command('nvfs-status') to inspect.",
+ "desc": "Core semantic indexer. NOT a WPM package — built into newos4.tar.gz. Use run_command('nvfs-status') to inspect.",
"url": "modules/core/module-neural-vfs.js",
"commands": [
"nvfs-index",
@@ -273,7 +273,7 @@
"media": {
"name": "Media Analysis",
"version": "1.0.0",
- "desc": "Image/audio/video analysis via Ollama vision models \u2014 analyze, describe, transcribe",
+ "desc": "Image/audio/video analysis via Ollama vision models — analyze, describe, transcribe",
"url": "modules/ext/media.js",
"commands": [
"analyze"
@@ -283,7 +283,7 @@
"three": {
"name": "3D Scene Builder",
"version": "1.0.0",
- "desc": "Three.js 3D scene builder \u2014 create, edit, visualize mindmaps in 3D, export standalone HTML",
+ "desc": "Three.js 3D scene builder — create, edit, visualize mindmaps in 3D, export standalone HTML",
"url": "modules/ext/three.js",
"commands": [
"three"
@@ -292,9 +292,19 @@
},
"kanban": {
"url": "https://webx11.duckdns.org/spaces/newos4/modules/ext/kanban.js",
- "description": "Kanban board \u2014 todo/doing/done task management with priorities",
+ "description": "Kanban board — todo/doing/done task management with priorities",
"category": "productivity",
"size": "6kb"
+ },
+ "course": {
+ "name": "Interactive Course",
+ "version": "1.0.0",
+ "desc": "Self-updating newOS4 course — chapters, walkthroughs, projects, AI tutor",
+ "url": "modules/ext/course.js",
+ "commands": [
+ "course"
+ ],
+ "category": "learning"
}
}
} \ No newline at end of file
diff --git a/scripts/course-daily-update.py b/scripts/course-daily-update.py
new file mode 100644
index 0000000..4f509d6
--- /dev/null
+++ b/scripts/course-daily-update.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python3
+"""Daily newOS4 course expansion. Grounded only: every command must exist in atlas.json."""
+import json, os, re, sys, subprocess
+from datetime import datetime, timezone
+import requests
+
+ROOT = '/var/www/webx11.duckdns.org/spaces/newos4'
+COURSE = os.path.join(ROOT, 'course')
+ATLAS, FEED, FAQ = (os.path.join(COURSE, n) for n in ('atlas.json', 'feed.json', 'faq.json'))
+STATE = os.path.join(COURSE, '_state.json'); CHANGELOG = os.path.join(COURSE, 'changelog.jsonl')
+PENDING = os.path.join(COURSE, 'pending')
+SEARCH = 'https://webx11.duckdns.org/api/search'
+OLLAMA = 'http://127.0.0.1:11434/v1/chat/completions'
+MODEL = 'deepseek-v4.1-flash:cloud'
+TOPICS = ['terminal UX best practices command design', 'browser virtual filesystem design',
+ 'module lazy loading cache busting web apps', 'Ollama local LLM tool calling best practices',
+ 'WebDAV client sync reliability', 'developer onboarding interactive tutorial design',
+ 'AI agent tool safety confirmation patterns', 'cron automation in single page apps',
+ 'privacy preserving product analytics', 'prompt engineering grounded answers no hallucination',
+ 'web terminal accessibility keyboard UX', 'self-hosted AI assistant cost control',
+ 'spaced repetition micro learning programming', 'secure API key handling in browser apps']
+SYS_DRAFT = ('You write ONE micro-lesson for the newOS4 terminal course. Return STRICT JSON only (no fences):\n'
+ '{"title":"...","minutes":4,"blocks":[{"type":"text|code|cmd|tip|warn","text":"..."}],'
+ '"exercise":{"instruction":"...","accept":["cmd ..."],"check":{"type":"manual"},"hint":"..."},"sources":["https://..."]}\n'
+ 'Rules: 3-5 blocks; at least one cmd block; every command MUST appear verbatim in the ALLOWED list; '
+ 'only state facts present in the research snippets; sources must be URLs from the snippets.')
+
+def load(p, d):
+ try:
+ with open(p, encoding='utf-8') as f: return json.load(f)
+ except Exception: return d
+
+def save(p, obj):
+ tmp = p + '.tmp'
+ with open(tmp, 'w', encoding='utf-8') as f: json.dump(obj, f, ensure_ascii=False, indent=1)
+ os.replace(tmp, p); os.chmod(p, 0o644)
+
+def parse_json(txt):
+ txt = (txt or '').strip()
+ if txt.startswith('`' * 3): txt = txt.split('\n', 1)[1] if '\n' in txt else ''
+ txt = txt.strip()
+ if txt.endswith('`' * 3): txt = txt[:-3].strip()
+ m = re.search(r'\{[\s\S]*\}', txt)
+ try: return json.loads(m.group(0) if m else txt)
+ except Exception: return None
+
+def llm(system, user, n=900):
+ try:
+ r = requests.post(OLLAMA, json={'model': MODEL, 'stream': False, 'temperature': 0.4, 'max_tokens': n,
+ 'messages': [{'role': 'system', 'content': system}, {'role': 'user', 'content': user}]}, timeout=180)
+ if not r.ok: return None
+ return (r.json().get('choices') or [{}])[0].get('message', {}).get('content')
+ except Exception:
+ return None
+
+def research(q):
+ try:
+ r = requests.get(SEARCH, params={'q': q}, timeout=25)
+ if not r.ok: return []
+ return [{'title': (x.get('title') or '')[:160], 'url': x['url'], 'snippet': (x.get('content') or '')[:600]}
+ for x in (r.json().get('results') or [])[:4] if x.get('url')]
+ except Exception:
+ return []
+
+def validate_item(it, known, urls):
+ if not isinstance(it, dict): return False, 'not object'
+ if not (10 <= len(it.get('title') or '') <= 90): return False, 'title'
+ blocks = it.get('blocks') or []
+ if not (3 <= len(blocks) <= 6): return False, 'block count'
+ if any(b.get('type') not in ('text', 'code', 'cmd', 'tip', 'warn', 'table') or not (b.get('text') or '').strip() for b in blocks): return False, 'block'
+ if not any(b.get('type') == 'cmd' for b in blocks): return False, 'no cmd'
+ ex = it.get('exercise') or {}
+ if not ex.get('instruction') or not ex.get('accept'): return False, 'exercise'
+ for a in ex['accept']:
+ if a.split(' ')[0] not in known: return False, 'unknown command ' + a.split(' ')[0]
+ if not any(str(s) in urls for s in (it.get('sources') or []) if str(s).startswith('http')): return False, 'no source'
+ return True, ''
+
+def main():
+ dry = '--dry-run' in sys.argv
+ now = datetime.now(timezone.utc); day = now.strftime('%Y-%m-%d')
+ state = load(STATE, {'seen_commands': [], 'topic_index': 0, 'last_run': None})
+ if not dry: subprocess.run([sys.executable, os.path.join(ROOT, 'scripts', 'gen-course-atlas.py')], check=True)
+ atlas = load(ATLAS, {}); known = set(atlas.get('commands', {}))
+ seen = set(state.get('seen_commands') or known)
+ new = sorted(known - seen) if state.get('seen_commands') else []
+ feed = load(FEED, {'version': '', 'items': []}); added = []; rejected = []
+ if new:
+ feed['items'].insert(0, {'id': 'new-' + day, 'date': day, 'title': f'{len(new)} new command(s) in newOS4', 'minutes': 2,
+ 'generated': False, 'sources': ['atlas.json'],
+ 'blocks': [{'type': 'text', 'text': 'Run `help <cmd>` for details.'}, {'type': 'cmd', 'text': '\n'.join(new[:12])}]})
+ added.append('new-' + day)
+ if not any(i.get('id') == 'daily-' + day for i in feed['items']):
+ topic = TOPICS[state.get('topic_index', 0) % len(TOPICS)]
+ results = research(topic)
+ if results:
+ snippets = '\n'.join(f"- {r['title']} | {r['url']}\n {r['snippet']}" for r in results)
+ draft = parse_json(llm(SYS_DRAFT, f'TOPIC: {topic}\n\nSNIPPETS:\n{snippets}\n\nALLOWED COMMANDS:\n' + ', '.join(sorted(known))))
+ ok, why = validate_item(draft, known, {r['url'] for r in results}) if draft else (False, 'no JSON')
+ if ok:
+ draft.update({'id': 'daily-' + day, 'date': day, 'generated': True})
+ feed['items'].insert(0, draft); added.append('daily-' + day)
+ else:
+ os.makedirs(PENDING, exist_ok=True)
+ save(os.path.join(PENDING, f'daily-{day}.json'), {'draft': draft, 'why': why, 'topic': topic, 'results': results})
+ rejected.append('daily: ' + why)
+ else:
+ rejected.append('daily: no research results')
+ state['topic_index'] = state.get('topic_index', 0) + 1
+ feed['items'] = feed['items'][:60]; feed['version'] = day
+ if not dry: save(FEED, feed)
+ if not dry: subprocess.run([sys.executable, os.path.join(ROOT, 'scripts', 'course-insights.py')], check=True)
+ insights = load(os.path.join(COURSE, 'insights.json'), {})
+ faq = load(FAQ, {'items': []}); have = {f.get('q') for f in faq['items']}; faq_n = 0
+ for q in (insights.get('top_questions') or [])[:5]:
+ if q['q'] in have: continue
+ draft = parse_json(llm('Answer a repeated newOS4 question. Return STRICT JSON only: {"a":"<=400 chars","commands":["cmd"],"sources":["atlas.json#cmd"]}. Only use ALLOWED commands.', f'QUESTION: {q["q"]}\nALLOWED COMMANDS:\n' + ', '.join(sorted(known)), 500))
+ if draft and draft.get('a') and all(c in known for c in draft.get('commands', [])):
+ faq['items'].append({'q': q['q'], 'a': draft['a'][:400], 'commands': draft['commands'],
+ 'sources': draft.get('sources') or ['insights.json'], 'added': day})
+ have.add(q['q']); faq_n += 1
+ else: rejected.append('faq: ' + q['q'][:60])
+ if not dry: save(FAQ, faq)
+ state.update({'seen_commands': sorted(known), 'last_run': now.strftime('%Y-%m-%dT%H:%M:%SZ')})
+ if not dry:
+ save(STATE, state)
+ with open(CHANGELOG, 'a', encoding='utf-8') as f:
+ f.write(json.dumps({'date': day, 'added': added, 'faq': faq_n, 'rejected': rejected,
+ 'atlas': f'{len(known)} commands'}, ensure_ascii=False) + '\n')
+ print('newOS4 Course daily — ' + day)
+ print('atlas: ' + str(len(known)) + ' commands | new: ' + str(len(new)))
+ print('daily lesson: ' + next((i['title'] for i in feed['items'] if i.get('id') == 'daily-' + day), 'none added'))
+ print('faq added: ' + str(faq_n) + ' | feed: ' + str(len(feed['items'])) + '/60')
+ print('rejected: ' + ('; '.join(rejected) if rejected else 'none'))
+ print('needs OK: none')
+
+if __name__ == '__main__':
+ sys.exit(main() or 0)
diff --git a/scripts/course-insights.py b/scripts/course-insights.py
new file mode 100644
index 0000000..0ee891e
--- /dev/null
+++ b/scripts/course-insights.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+"""Aggregate course events -> course/insights.json (counts + capped question text, no sids/IPs)."""
+import json, os, glob, re
+from collections import Counter
+from datetime import datetime, timezone
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+DATA = os.path.join(ROOT, 'data', 'course')
+OUT = os.path.join(ROOT, 'course', 'insights.json')
+
+def norm_q(q):
+ q = re.sub(r'https?://\S+', '', q or '')
+ return re.sub(r'\s+', ' ', q).strip().lower()[:200]
+
+def main():
+ opens, completes, fails, cmds, questions = Counter(), Counter(), Counter(), Counter(), Counter()
+ projects = Counter()
+ files = sorted(glob.glob(os.path.join(DATA, 'events-*.jsonl')))[-2:]
+ for path in files:
+ with open(path, encoding='utf-8', errors='ignore') as f:
+ for line in f:
+ try: e = json.loads(line)
+ except Exception: continue
+ t, d = e.get('type'), e.get('detail') or {}
+ lesson = (d.get('lesson') or '')[:40]
+ if t == 'lesson_open' and lesson: opens[lesson] += 1
+ elif t == 'lesson_complete' and lesson: completes[lesson] += 1
+ elif t == 'exercise_fail':
+ if lesson: fails[lesson] += 1
+ c = (d.get('command') or '').strip().split(' ')[0][:40]
+ if c: cmds[c] += 1
+ elif t == 'question':
+ q = norm_q(d.get('q'))
+ if len(q) >= 8: questions[q] += 1
+ elif t in ('project_open', 'project_view'):
+ p = (d.get('project') or '')[:40]
+ if p: projects[p] += 1
+ lessons = {}
+ for lid in set(opens) | set(completes):
+ o, c = opens[lid], completes[lid]
+ lessons[lid] = {'opens': o, 'completes': c, 'failures': fails.get(lid, 0),
+ 'completion_rate': round(c / o, 2) if o else 0.0}
+ payload = {'generated': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'),
+ 'window_files': [os.path.basename(p) for p in files],
+ 'lessons': dict(sorted(lessons.items())),
+ 'top_failed_commands': [{'cmd': c, 'n': n} for c, n in cmds.most_common(20)],
+ 'top_questions': [{'q': q, 'n': n} for q, n in questions.most_common(20) if n >= 2],
+ 'projects': dict(projects),
+ 'totals': {'opens': sum(opens.values()), 'completes': sum(completes.values()), 'failures': sum(fails.values())}}
+ os.makedirs(os.path.dirname(OUT), exist_ok=True)
+ tmp = OUT + '.tmp'
+ with open(tmp, 'w', encoding='utf-8') as f: json.dump(payload, f, ensure_ascii=False, indent=1)
+ os.replace(tmp, OUT); os.chmod(OUT, 0o644)
+ print(json.dumps({'lessons': len(lessons), 'questions': len(payload['top_questions']), 'totals': payload['totals']}))
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/course-validate.py b/scripts/course-validate.py
new file mode 100644
index 0000000..9b5dcf4
--- /dev/null
+++ b/scripts/course-validate.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python3
+"""Validate newOS4 course content. Exit 0 = publishable, 1 = errors printed."""
+import json, os, glob, sys
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+COURSE = os.path.join(ROOT, 'course')
+ATLAS = os.path.join(COURSE, 'atlas.json')
+errors, warnings = [], []
+
+def load(p):
+ try:
+ with open(p, encoding='utf-8') as f:
+ return json.load(f)
+ except Exception as e:
+ errors.append(f'{os.path.relpath(p, ROOT)}: invalid JSON: {e}')
+ return None
+
+def main():
+ manifest = load(os.path.join(COURSE, 'course.json'))
+ if not manifest:
+ return finish()
+ ids, lesson_ids = set(), set()
+ for ch in manifest.get('chapters', []):
+ cid = ch.get('id')
+ if not cid:
+ errors.append('course.json: chapter missing id'); continue
+ if cid in ids:
+ errors.append(f'course.json: duplicate chapter id {cid}')
+ ids.add(cid)
+ path = os.path.join(COURSE, 'chapters', cid + '.json')
+ if not os.path.exists(path):
+ errors.append(f'missing chapter file {cid}.json'); continue
+ chd = load(path)
+ if not chd: continue
+ man_ids = [x.get('id') for x in ch.get('lessons', [])]
+ file_ids = [x.get('id') for x in chd.get('lessons', [])]
+ if man_ids != file_ids:
+ errors.append(f'{cid}: manifest lessons {man_ids} != chapter file lessons {file_ids}')
+ for l in chd.get('lessons', []):
+ lid = l.get('id')
+ if not lid:
+ errors.append(f'{cid}: lesson missing id'); continue
+ if lid in lesson_ids:
+ errors.append(f'duplicate lesson id {lid}')
+ lesson_ids.add(lid)
+ for k in ('title', 'objective'):
+ if not l.get(k): errors.append(f'{lid}: missing {k}')
+ mins = l.get('minutes', 0)
+ if not isinstance(mins, int) or isinstance(mins, bool) or mins < 1:
+ errors.append(f'{lid}: minutes must be >=1')
+ blocks = l.get('blocks') or []
+ if len(blocks) < 3: errors.append(f'{lid}: needs >=3 blocks')
+ for b in blocks:
+ if b.get('type') not in ('text', 'code', 'cmd', 'tip', 'warn', 'table'):
+ errors.append(f'{lid}: bad block type {b.get("type")!r}')
+ ex = l.get('exercise')
+ if ex:
+ if not ex.get('instruction') or not ex.get('accept'):
+ errors.append(f'{lid}: exercise needs instruction+accept')
+ chk = ex.get('check', {})
+ if chk.get('type') not in ('command', 'file', 'output', 'manual'):
+ errors.append(f'{lid}: exercise check.type invalid')
+ if chk.get('type') == 'file' and not chk.get('path'):
+ errors.append(f'{lid}: file check requires path')
+ if chk.get('type') == 'output' and not chk.get('contains'):
+ errors.append(f'{lid}: output check requires contains')
+ if not ex.get('hint'): warnings.append(f'{lid}: exercise without hint')
+ if l.get('generated') and not l.get('sources'):
+ errors.append(f'{lid}: generated lesson needs sources')
+ if os.path.exists(ATLAS):
+ atlas = load(ATLAS) or {}
+ known = set(atlas.get('commands', {}))
+ for f in glob.glob(os.path.join(COURSE, 'chapters', '*.json')):
+ chd = load(f) or {}
+ for l in chd.get('lessons', []):
+ for c in l.get('commands', []):
+ if c not in known:
+ errors.append(f'{l.get("id")}: unknown command {c}')
+ for name, key in (('feed.json', 'items'), ('faq.json', 'items')):
+ p = os.path.join(COURSE, name)
+ if not os.path.exists(p): continue
+ d = load(p) or {}
+ for it in d.get(key, []):
+ if isinstance(it, dict) and it.get('date') is None:
+ warnings.append(f'{name}: item without date')
+ return finish()
+
+def finish():
+ for w in warnings: print(f'WARN {w}')
+ for e in errors: print(f'ERROR {e}')
+ print(f'course-validate: {len(errors)} error(s), {len(warnings)} warning(s)')
+ return 1 if errors else 0
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/scripts/gen-course-atlas.py b/scripts/gen-course-atlas.py
new file mode 100644
index 0000000..559fd4c
--- /dev/null
+++ b/scripts/gen-course-atlas.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+"""Build course/atlas.json from newOS4 source. Ground truth for every lesson claim."""
+import json, os, re, glob, hashlib
+from datetime import datetime, timezone
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+OUT = os.path.join(ROOT, 'course', 'atlas.json')
+CORE_DIR, EXT_DIR = os.path.join(ROOT, 'modules', 'core'), os.path.join(ROOT, 'modules', 'ext')
+SKIP = {'core-bundle.js', 'critical-ext-bundle.js', 'sandbox-worker.js'}
+
+CATEGORY = [
+ ('module-fs', 'filesystem'), ('virtual-fs', 'filesystem'), ('module-aiassist', 'ai'), ('module-ollama', 'ai'),
+ ('module-memory-graph', 'ai'), ('module-smartctx', 'ai'), ('module-neural-vfs', 'ai'), ('module-world-model', 'ai'),
+ ('module-sandbox', 'ai'), ('module-searxng', 'web'), ('module-fetcher', 'web'), ('webdav', 'cloud'),
+ ('editor', 'editor'), ('filemgr', 'filesystem'), ('finder', 'filesystem'), ('diff', 'editor'),
+ ('cron', 'automation'), ('kanban', 'automation'), ('shell', 'shell'), ('busybox', 'shell'),
+ ('theme', 'ui'), ('floating', 'ui'), ('voice', 'ui'), ('three', 'ui'), ('console', 'dev'), ('debug-panel', 'dev'),
+ ('wpm', 'system'), ('tutorial', 'learning'), ('skill-pack-core', 'ai'), ('cryptogzip', 'system'),
+ ('module-agent-registry', 'agents'), ('module-orchestrator', 'agents'), ('module-leases', 'agents'),
+ ('module-chatstudio', 'ai'), ('module-servermon', 'system'), ('module-config', 'system'), ('module-system', 'system'),
+ ('course', 'learning'),
+]
+
+def category_for(filename, cmd):
+ for frag, cat in CATEGORY:
+ if frag in filename: return cat
+ if cmd.startswith('webdav'): return 'cloud'
+ if cmd.startswith('cron') or cmd.startswith('kanban'): return 'automation'
+ if cmd.startswith('ai-') or cmd in ('ai', 'ask'): return 'ai'
+ return 'general'
+
+def tier_lists():
+ rebuild = open(os.path.join(ROOT, 'scripts', 'rebuild-bundles.py')).read()
+ def listed(name):
+ m = re.search(name + r"\s*=\s*\[([^\]]+)\]", rebuild, re.S)
+ return set(re.findall(r"([a-z0-9-]+\.js)", m.group(1))) if m else set()
+ loader = open(os.path.join(ROOT, 'modules', 'core', 'module-loader.js')).read()
+ m = re.search(r'extKeys\s*=\s*\[([^\]]+)\]', loader)
+ ext = set(re.findall(r"'([a-z0-9-]+)'", m.group(1))) if m else set()
+ return listed('CORE_FILES'), listed('CRITICAL_EXT_FILES'), ext
+
+CORE_FILES, CRITICAL_EXT_FILES, EXT_KEYS = tier_lists()
+
+def module_tier(filename):
+ base = os.path.basename(filename)
+ if base in CRITICAL_EXT_FILES: return 'critical-ext-bundle'
+ if base in CORE_FILES: return 'core-bundle'
+ if base[:-3] in EXT_KEYS: return 'ext-auto'
+ return 'ext-manual'
+
+def desc_map(text):
+ out = {}
+ for m in re.finditer(r"'([a-z0-9][a-z0-9_-]{1,40})'\s*:\s*'((?:[^'\\]|\\.){8,300})'", text):
+ cmd, desc = m.group(1), m.group(2)
+ if len(desc) < 10 or ' ' not in desc or desc.endswith('.js'):
+ continue
+ out.setdefault(cmd, desc.replace("\\'", "'"))
+ return out
+
+def main():
+ commands, modules = {}, {}
+ files = sorted(glob.glob(os.path.join(CORE_DIR, '*.js')) + glob.glob(os.path.join(EXT_DIR, '*.js')))
+ for path in files:
+ base = os.path.basename(path)
+ if base in SKIP: continue
+ text = open(path, encoding='utf-8', errors='ignore').read()
+ cmds = sorted(set(re.findall(r"commands\['([A-Za-z0-9_-]+)'\]\s*=", text)))
+ if not cmds: continue
+ modname = base[:-3]
+ modules[modname] = {'file': os.path.relpath(path, ROOT), 'tier': module_tier(path),
+ 'commands': cmds, 'lines': text.count('\n') + 1}
+ dm = desc_map(text)
+ for c in cmds:
+ commands[c] = {'module': modname, 'file': os.path.relpath(path, ROOT), 'tier': module_tier(path),
+ 'category': category_for(base, c), 'desc': dm.get(c, ''),
+ 'builtin': False, 'example': c}
+ term = open(os.path.join(ROOT, 'modules', 'core', 'terminal.js'), encoding='utf-8').read()
+ for c in sorted(set(re.findall(r"cmd === '([a-z0-9-]+)'", term))):
+ if c in commands: continue
+ commands[c] = {'module': 'terminal', 'file': 'modules/core/terminal.js', 'tier': 'core-bundle',
+ 'category': 'system', 'desc': '', 'builtin': True, 'example': c}
+ idx_path = os.path.join(ROOT, 'modules-index.json')
+ mod_by_cmd = {}
+ if os.path.exists(idx_path):
+ idx = json.load(open(idx_path))
+ for m in idx.get('modules', {}).values():
+ for c in m.get('commands', []):
+ mod_by_cmd.setdefault(c, m)
+ for c, e in commands.items():
+ if not e['desc']:
+ m = mod_by_cmd.get(c) or {}
+ e['desc'] = (m.get('description') or f'{c} — command from {e["module"]}')[:300]
+ payload = {
+ 'version': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'),
+ 'source_hash': hashlib.sha256(''.join(sorted(commands)).encode()).hexdigest()[:16],
+ 'commands': dict(sorted(commands.items())),
+ 'modules': dict(sorted(modules.items())),
+ 'coverage': {'commands': len(commands),
+ 'described': sum(1 for e in commands.values() if e['desc']),
+ 'modules': len(modules),
+ 'tiers': {t: sum(1 for e in commands.values() if e['tier'] == t)
+ for t in ('core-bundle', 'critical-ext-bundle', 'ext-auto', 'ext-manual')}},
+ }
+ os.makedirs(os.path.dirname(OUT), exist_ok=True)
+ tmp = OUT + '.tmp'
+ with open(tmp, 'w', encoding='utf-8') as f:
+ json.dump(payload, f, ensure_ascii=False, indent=1)
+ os.replace(tmp, OUT)
+ os.chmod(OUT, 0o644)
+ print(json.dumps(payload['coverage'], ensure_ascii=False))
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/newos4-maintain.py b/scripts/newos4-maintain.py
new file mode 100644
index 0000000..6321a3f
--- /dev/null
+++ b/scripts/newos4-maintain.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python3
+"""newOS4 maintenance: drift, bundles, syntax, perms, data hygiene, live health.
+Safe fixes only with --fix. Never touches nginx, services, sudo, or user content."""
+import glob, json, os, re, subprocess, sys
+from datetime import datetime, timezone
+
+ROOT = '/var/www/webx11.duckdns.org/spaces/newos4'
+WEB = 'https://webx11.duckdns.org/spaces/newos4'
+FIX = '--fix' in sys.argv
+DEEP = '--deep' in sys.argv
+issues = []
+def add(l, m): issues.append((l, m))
+def run(cmd, **kw): return subprocess.run(cmd, capture_output=True, text=True, **kw)
+def sh(cmd): return run(cmd, shell=True).stdout.strip()
+
+def check_versions():
+ html = open(os.path.join(ROOT, 'main.html'), encoding='utf-8').read()
+ loader = open(os.path.join(ROOT, 'modules/core/module-loader.js'), encoding='utf-8').read()
+ m = re.search(r"const LATEST_VER\s*=\s*'([^']+)'", loader)
+ ver = m.group(1) if m else '?'
+ n = html.count(ver)
+ add('PASS' if n == 3 else 'FAIL', f'version {ver} x{n} in main.html (expect 3)')
+
+def check_bundles():
+ paths = ('modules/core/core-bundle.js', 'modules/ext/critical-ext-bundle.js')
+ before = {p: sh(f'md5sum {p}') for p in paths}
+ if not FIX:
+ add('PASS', 'bundle check skipped (use --fix)'); return
+ r = run([sys.executable, 'scripts/rebuild-bundles.py'], cwd=ROOT)
+ after = {p: sh(f'md5sum {p}') for p in paths}
+ changed = [p for p in paths if before[p] != after[p]]
+ add('FAIL' if r.returncode else ('WARN' if changed else 'PASS'),
+ 'bundles ' + ('FAILED' if r.returncode else ('rebuilt: ' + ', '.join(changed) if changed else 'clean')))
+
+def check_syntax():
+ bad = []
+ for p in glob.glob(os.path.join(ROOT, 'modules/core/*.js')) + glob.glob(os.path.join(ROOT, 'modules/ext/*.js')):
+ if os.path.basename(p) in ('core-bundle.js', 'critical-ext-bundle.js'): continue
+ if run(['node', '--check', p]).returncode: bad.append(os.path.basename(p))
+ add('PASS' if not bad else 'FAIL', 'syntax ' + ('OK' if not bad else 'fail: ' + ', '.join(bad)))
+
+def check_perms():
+ targets = glob.glob(os.path.join(ROOT, 'course/*.json')) + glob.glob(os.path.join(ROOT, 'course/chapters/*.json')) \
+ + [os.path.join(ROOT, 'modules/ext/course.js'), os.path.join(ROOT, 'course.html'), os.path.join(ROOT, 'api/course/event.php')]
+ bad = []
+ for p in targets:
+ if not os.path.exists(p): continue
+ if (os.stat(p).st_mode & 0o777) != 0o644 or sh(f'stat -c %G {p}') != 'www-data':
+ if FIX:
+ os.chmod(p, 0o644); sh(f'chgrp www-data {p}')
+ else: bad.append(os.path.basename(p))
+ add('PASS' if not bad else ('WARN' if FIX else 'FAIL'), 'perms ' + ('clean' if not bad else ('fixed' if FIX else 'bad: ' + ', '.join(bad[:5]))))
+
+def check_index():
+ idxp = os.path.join(ROOT, 'modules-index.json')
+ atlas = json.load(open(os.path.join(ROOT, 'course/atlas.json')))
+ idx = json.load(open(idxp))
+ cur = len(set(c for m in idx.get('modules', {}).values() for c in m.get('commands', [])))
+ if cur == atlas['coverage']['commands']:
+ add('PASS', f'modules-index {cur} commands'); return
+ add('WARN', f'modules-index stale {cur} vs {atlas["coverage"]["commands"]}')
+ if not FIX: return
+ for name, m in atlas['modules'].items():
+ e = idx['modules'].get(name, {'name': name, 'file': m['file'], 'type': 'core' if m['tier'] == 'core-bundle' else 'ext',
+ 'class': '', 'auto_load': m['tier'] != 'ext-manual', 'description': ''})
+ e.update({'commands': m['commands'], 'file': m['file'], 'tier': m['tier']})
+ idx['modules'][name] = e
+ idx['version'] = datetime.now(timezone.utc).strftime('%Y-%m-%d') + '-atlas'
+ tmp = idxp + '.tmp'
+ with open(tmp, 'w', encoding='utf-8') as f: json.dump(idx, f, ensure_ascii=False, indent=1)
+ os.replace(tmp, idxp); os.chmod(idxp, 0o644); sh(f'chgrp www-data {idxp}')
+ add('PASS', 'modules-index regenerated')
+
+def check_data():
+ err = os.path.join(ROOT, 'data/debug/errors.json')
+ if os.path.exists(err) and os.path.getsize(err) > 5 * 1024 * 1024:
+ if FIX:
+ arc = os.path.join(ROOT, 'data/debug/archive'); os.makedirs(arc, exist_ok=True)
+ os.replace(err, os.path.join(arc, 'errors-' + datetime.now().strftime('%Y%m') + '.json'))
+ open(err, 'w').write('[]'); os.chmod(err, 0o664); sh(f'chgrp www-data {err}')
+ add('PASS', 'errors.json rotated')
+ else: add('WARN', 'errors.json >5MB')
+ else: add('PASS', 'debug data OK')
+
+def check_live():
+ for url in (f'{WEB}/main.html', f'{WEB}/course.html', f'{WEB}/course/course.json', f'{WEB}/modules/core/core-bundle.js'):
+ code = sh(f"curl -s -o /dev/null -w '%{{http_code}}' '{url}'")
+ add('PASS' if code == '200' else 'FAIL', f'HTTP {code} {url.replace(WEB, "")}')
+
+def check_course():
+ r = run([sys.executable, 'scripts/course-validate.py'], cwd=ROOT)
+ add('PASS' if r.returncode == 0 else 'FAIL', 'course ' + ('valid' if r.returncode == 0 else 'INVALID: ' + r.stdout[-160:]))
+
+def check_links():
+ if not DEEP: return
+ broken = []
+ for page in ('main.html', 'course.html', 'help.html', 'modules.html', 'guide.html'):
+ p = os.path.join(ROOT, page)
+ if not os.path.exists(p): continue
+ for href in re.findall(r'(?:href|src)="([^"#]+)"', open(p, encoding='utf-8').read()):
+ if any(x in href for x in ("'", '+', '{', '}')): continue
+ if href.startswith(('http', '//', 'mailto:', 'javascript:', 'data:', '/api/')): continue
+ target = os.path.normpath(os.path.join(ROOT, href.split('?')[0]))
+ if not os.path.exists(target): broken.append(f'{page}:{href}')
+ add('PASS' if not broken else 'WARN', 'links ' + ('OK' if not broken else f'{len(broken)} broken (first: {broken[0]})'))
+
+def main():
+ os.chdir(ROOT)
+ check_versions(); check_bundles(); check_syntax(); check_perms(); check_index(); check_data(); check_live(); check_course(); check_links()
+ fails = [m for l, m in issues if l == 'FAIL']
+ print('newOS4 maintenance — ' + datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%MZ') + (' [fix]' if FIX else '') + (' [deep]' if DEEP else ''))
+ for l, m in issues: print(f'{l}: {m}')
+ print('maintenance result: ' + ('FAIL' if fails else 'OK'))
+ print('needs OK: ' + ('none' if not fails else '; '.join(fails)[:400]))
+ return 1 if fails else 0
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/scripts/test-course.mjs b/scripts/test-course.mjs
new file mode 100644
index 0000000..7a64dc8
--- /dev/null
+++ b/scripts/test-course.mjs
@@ -0,0 +1,59 @@
+import fs from 'fs';
+import vm from 'vm';
+const SRC = '/var/www/webx11.duckdns.org/spaces/newos4/modules/ext/course.js';
+const MANIFEST = { version: 'test-1', updated: '2026-10-18', chapters: [
+ { id: '01-orientation', title: 'Orientation', lessons: [{ id: '1.1', title: 'T', objective: 'O', minutes: 5 }] }
+], projects: ['p1'] };
+const CHAPTER = { id: '01-orientation', title: 'Orientation', lessons: [{ id: '1.1', title: 'T', objective: 'O', minutes: 5,
+ blocks: [{ type: 'text', text: 'hello' }, { type: 'cmd', text: 'status' }, { type: 'tip', text: 'tip' }],
+ exercise: { instruction: 'touch ok.txt', accept: ['touch ok.txt'], check: { type: 'file', path: '/ok.txt' }, hint: 'touch ok.txt' } }] };
+let printed = [];
+const terminal = {
+ print(t) { printed.push(String(t)); },
+ fs: { _f: {}, readFile(p) { return Object.prototype.hasOwnProperty.call(this._f, p) ? this._f[p] : null; },
+ writeFile(p, c) { this._f[p] = c; }, mkdirRecursive() {}, fsReadJSON(p) { try { return JSON.parse(this._f[p] || 'null'); } catch (e) { return null; } },
+ fsWriteJSON(p, o) { this._f[p] = JSON.stringify(o); } },
+ _pushPrintCapture(cb) { this._cap = cb; }, _popPrintCapture() { this._cap && this._cap('output'); },
+ ollamaManager: { currentModel: 'test-model' }
+};
+const store = {};
+const sandbox = {
+ window: { webx11: { moduleManager: { registerModule() {} } } },
+ localStorage: { getItem: k => store[k] ?? null, setItem: (k, v) => { store[k] = String(v); }, removeItem: k => { delete store[k]; } },
+ fetch: async (url) => {
+ if (url.includes('course.json')) return { ok: true, json: async () => MANIFEST };
+ if (url.includes('atlas.json')) return { ok: true, json: async () => ({ commands: { status: { desc: 'Status', module: 'system', tier: 'core-bundle', category: 'system' }, touch: { desc: 'Touch', module: 'fs', tier: 'core-bundle', category: 'filesystem' }, ls: { desc: 'List', module: 'fs', tier: 'core-bundle', category: 'filesystem' } } }) };
+ if (url.includes('chapters/')) return { ok: true, json: async () => CHAPTER };
+ if (url.includes('event.php')) return { ok: true, json: async () => ({}) };
+ if (url.includes('/ai/')) return { ok: true, json: async () => ({ choices: [{ message: { content: 'answer' } }] }) };
+ return { ok: false, status: 404, json: async () => ({}) };
+ },
+ console, document: { body: {} }, setTimeout, clearTimeout, Date, Math, JSON, RegExp
+};
+sandbox.globalThis = sandbox;
+sandbox.webx11 = sandbox.window.webx11;
+vm.createContext(sandbox);
+vm.runInContext(fs.readFileSync(SRC, 'utf8') + '\n;globalThis.__CourseModule = CourseModule;', sandbox);
+const mod = new sandbox.__CourseModule();
+mod.terminal = terminal;
+let pass = 0, fail = 0;
+const check = (n, c, got) => { if (c) { pass++; console.log('PASS', n); } else { fail++; console.log('FAIL', n, got || ''); } };
+const run = async () => {
+ mod.registerCommands();
+ await mod._warm();
+ await mod.handle(['list']);
+ check('list mentions Orientation', printed.join('\n').includes('Orientation'));
+ await mod.handle(['open', '1.1']);
+ check('lesson printed', printed.join('\n').includes('Exercise'));
+ mod.onCommandStart('touch', ['ok.txt']); terminal.fs.writeFile('/ok.txt', 'x'); mod.onCommandExecuted('touch', ['ok.txt']);
+ check('file exercise completes', !!mod._progress().completed['1.1'], JSON.stringify(mod._progress()));
+ check('xp awarded', mod._progress().xp >= 10);
+ check('match exact', mod._match('ls', 'ls') && !mod._match('ls', 'ls -la'));
+ check('match wildcard', mod._match('touch *', 'touch a.txt'));
+ await mod.handle(['ask', 'what is status?']);
+ check('tutor answered', printed.join('\n').includes('answer'));
+ check('skill installed', !!terminal.fs._f['/skills/course-01-orientation.md']);
+ console.log(`RESULT ${pass} passed, ${fail} failed`);
+ process.exit(fail ? 1 : 0);
+};
+run().catch(e => { console.error(e); process.exit(1); });