class BusyBoxModule { constructor() { this.commands = {}; this.terminal = null; } requirements() { return { met: true, missing: [] }; } getHelpInfo() { return { 'which': 'which — Locate a command in the module registry', 'env': 'env — List environment variables (webx11_ keys + user profile)', 'date': 'date — Show current date/time (ISO, local, UTC, timestamp)', 'cal': 'cal [month] [year] — Display an ASCII calendar', 'yes': 'yes [string] — Output repeated string (default "y", capped at 50)', 'seq': 'seq or seq — Generate sequence of numbers', 'base64': 'base64 [-d] — Base64 encode/decode', 'printf': 'printf [args...] — Formatted print (%s, %d, %f, \\n, \\t)', 'cut': 'cut -f [-d] — Extract column from file', 'uniq': 'uniq — Filter adjacent duplicate lines from a file', 'hostname': 'hostname — Show host/user-agent info', 'whoami': 'whoami — Show current user name', 'basename': 'basename — Strip directory from path', 'dirname': 'dirname — Strip filename from path', 'sleep': 'sleep — Delay execution with countdown', 'expand': 'expand — Convert tabs to spaces (default 4) in file', }; } registerCommands() { this.commands['which'] = (args) => this._cmdWhich(args); this.commands['env'] = () => this._cmdEnv(); this.commands['date'] = (args) => this._cmdDate(args); this.commands['cal'] = (args) => this._cmdCal(args); this.commands['yes'] = (args) => this._cmdYes(args); this.commands['seq'] = (args) => this._cmdSeq(args); this.commands['base64'] = (args) => this._cmdBase64(args); this.commands['printf'] = (args) => this._cmdPrintf(args); this.commands['cut'] = (args) => this._cmdCut(args); this.commands['uniq'] = (args) => this._cmdUniq(args); this.commands['hostname'] = () => this._cmdHostname(); this.commands['whoami'] = () => this._cmdWhoami(); this.commands['basename'] = (args) => this._cmdBasename(args); this.commands['dirname'] = (args) => this._cmdDirname(args); this.commands['sleep'] = (args) => this._cmdSleep(args); this.commands['expand'] = (args) => this._cmdExpand(args); } // ── which ── _cmdWhich(args) { if (!args[0]) return this.terminal.print('Usage: which ', 'warning'); const query = args[0].toLowerCase(); const all = this.terminal.moduleManager.getAllCommands(); const keys = Object.keys(all); if (all[query]) { let foundMod = null; for (const modName in this.terminal.moduleManager.modules) { const mod = this.terminal.moduleManager.modules[modName]; if (mod.commands && mod.commands[query]) { foundMod = mod; break; } } const src = foundMod ? ` (from ${foundMod.constructor.name})` : ''; return this.terminal.print(`${query}${src}`, 'info'); } // Fuzzy match: check Levenshtein distance up to 2 let best = null, bestDist = 3; for (const k of keys) { const d = this._levenshtein(query, k); if (d < bestDist) { best = k; bestDist = d; } } if (best) { this.terminal.print(`which: no '${query}' in registry`, 'error'); this.terminal.print(`Did you mean '${best}'?`, 'info'); } else { this.terminal.print(`which: no '${query}' in registry`, 'error'); } } _levenshtein(a, b) { const m = a.length, n = b.length; const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); for (let i = 0; i <= m; i++) dp[i][0] = i; for (let j = 0; j <= n; j++) dp[0][j] = j; for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] : 1 + Math.min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]); } } return dp[m][n]; } // ── env ── _cmdEnv() { const lines = []; // webx11_ localStorage keys for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i); if (k && k.startsWith('webx11_')) { let v = localStorage.getItem(k) || ''; if (v.length > 80) v = v.substring(0, 77) + '...'; lines.push(`${k}=${v}`); } } // Runtime info if (this.terminal.ollamaManager) { lines.push(`OLLAMA_MODEL=${this.terminal.ollamaManager.currentModel || 'none'}`); lines.push(`OLLAMA_CONNECTED=${this.terminal.ollamaManager.connected}`); } lines.push(`PWD=/${this.terminal.fs.currentPath.join('/')}`); lines.push(`SHELL=webx11-terminal`); lines.push(`USER=darkbot`); lines.push(`HOME=/`); lines.push(`TERM=xterm-256color`); const prof = this.terminal.moduleManager.modules['WebX11AIAssistantModule']; if (prof && prof._userProfile) { const facts = prof._userProfile.knownFacts; if (facts) { for (const [k, v] of Object.entries(facts)) { lines.push(`USER_${k.toUpperCase()}=${v}`); } } } this.terminal.print(lines.join('\n'), 'info'); } // ── date ── _cmdDate(args) { const n = new Date(); if (args.length > 0) { const flag = args[0].toLowerCase(); if (flag === '-u' || flag === '--utc' || flag === 'utc') return this.terminal.print(n.toUTCString(), 'info'); if (flag === '-i' || flag === '--iso' || flag === 'iso') return this.terminal.print(n.toISOString(), 'info'); if (flag === '-t' || flag === '--ts' || flag === 'timestamp') return this.terminal.print(String(n.getTime()), 'info'); if (flag === '-r' || flag === '--rfc' || flag === 'rfc') return this.terminal.print(n.toUTCString(), 'info'); } const local = n.toLocaleString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', timeZoneName: 'short' }); this.terminal.print(local, 'info'); this.terminal.print(`ISO: ${n.toISOString()}`, 'info'); this.terminal.print(`UTC: ${n.toUTCString()}`, 'info'); this.terminal.print(`Unix: ${Math.floor(n.getTime() / 1000)}`, 'info'); } // ── cal ── _cmdCal(args) { const now = new Date(); let year = now.getFullYear(); let month = now.getMonth() + 1; if (args.length >= 2) { month = parseInt(args[0]); year = parseInt(args[1]); } else if (args.length === 1) { // If only one arg, could be year (4 digits) or month const v = parseInt(args[0]); if (args[0].length === 4 || v > 12) { year = v; } else { month = v; } } if (isNaN(month) || isNaN(year) || month < 1 || month > 12) { return this.terminal.print('cal: invalid month or year', 'error'); } const monthNames = ['January','February','March','April','May','June', 'July','August','September','October','November','December']; const first = new Date(year, month - 1, 1); const last = new Date(year, month, 0); const daysInMonth = last.getDate(); const startDay = first.getDay(); // 0=Sun const header = ` ${monthNames[month-1]} ${year}`; const weekdays = 'Su Mo Tu We Th Fr Sa'; let cal = header + '\n' + weekdays + '\n'; let line = ' '.repeat(startDay); for (let d = 1; d <= daysInMonth; d++) { line += String(d).padStart(2) + ' '; if ((startDay + d) % 7 === 0 || d === daysInMonth) { cal += line.trimEnd() + '\n'; line = ''; } } this.terminal.print(cal, 'info'); } // ── yes ── _cmdYes(args) { const text = args.length > 0 ? args.join(' ') : 'y'; const count = 50; for (let i = 0; i < count; i++) { this.terminal.print(text); } this.terminal.print(`(repeated ${count} times, capped)`, 'text-muted'); } // ── seq ── _cmdSeq(args) { if (args.length === 0) return this.terminal.print('Usage: seq or seq ', 'warning'); let first = 1, last; if (args.length === 1) { last = parseInt(args[0]); } else { first = parseInt(args[0]); last = parseInt(args[1]); } if (isNaN(first) || isNaN(last)) return this.terminal.print('seq: non-integer argument', 'error'); const max = 1000; if (last - first > max) return this.terminal.print(`seq: range too large (max ${max})`, 'error'); const output = []; if (first <= last) { for (let i = first; i <= last; i++) output.push(String(i)); } else { for (let i = first; i >= last; i--) output.push(String(i)); } this.terminal.print(output.join('\n'), 'info'); } // ── base64 ── _cmdBase64(args) { if (args.length === 0) return this.terminal.print('Usage: base64 [-d] ', 'warning'); if (args[0] === '-d' || args[0] === '--decode') { if (args.length < 2) return this.terminal.print('Usage: base64 -d ', 'warning'); const encoded = args.slice(1).join(' '); try { const decoded = atob(encoded); this.terminal.print(decoded, 'info'); } catch (e) { this.terminal.print('base64: invalid input (not valid base64)', 'error'); } } else { const input = args.join(' '); this.terminal.print(btoa(input), 'info'); } } // ── printf ── _cmdPrintf(args) { if (args.length === 0) return this.terminal.print('Usage: printf [args...]', 'warning'); let fmt = args[0]; const vals = args.slice(1); let vi = 0; // Replace format specifiers step by step const result = fmt .replace(/\\n/g, '\n') .replace(/\\t/g, '\t') .replace(/\\\\/g, '\\') .replace(/%s/g, () => vals[vi++] || '') .replace(/%d/g, () => { const v = parseInt(vals[vi++]); return isNaN(v) ? '0' : String(v); }) .replace(/%f/g, () => { const v = parseFloat(vals[vi++]); return isNaN(v) ? '0.000000' : v.toFixed(6); }) .replace(/%[^sdf%]/g, (m) => `(unsupported: ${m})`); this.terminal.print(result, 'info'); } // ── cut ── _cmdCut(args) { if (args.length < 2) return this.terminal.print('Usage: cut -f [-d] ', 'warning'); let field = null, delim = '\t', file = ''; for (const a of args) { if (a.startsWith('-f')) field = parseInt(a.substring(2)); else if (a.startsWith('-d')) delim = a.substring(2); else file = a; } if (!field || isNaN(field)) return this.terminal.print('cut: -f flag required', 'error'); if (!file) return this.terminal.print('cut: file argument required', 'error'); const content = this.terminal.fs.readFile(file); if (content === null) return this.terminal.print(`cut: ${file}: No such file`, 'error'); const lines = content.split('\n').filter(l => l); const output = []; for (const line of lines) { const parts = line.split(delim); const idx = field - 1; if (idx >= 0 && idx < parts.length) { output.push(parts[idx]); } } this.terminal.print(output.join('\n'), 'info'); } // ── uniq ── _cmdUniq(args) { if (!args[0]) return this.terminal.print('Usage: uniq ', 'warning'); const content = this.terminal.fs.readFile(args[0]); if (content === null) return this.terminal.print(`uniq: ${args[0]}: No such file`, 'error'); const lines = content.split('\n'); let last = null; const output = []; for (const line of lines) { if (line !== last) { output.push(line); last = line; } } this.terminal.print(output.join('\n'), 'info'); } // ── hostname ── _cmdHostname() { const ua = navigator.userAgent; const space = 'newos4'; const platform = navigator.platform || 'unknown'; const version = 'WebX11 ' + (window.NEWOS4_VERSION || 'v4.0'); this.terminal.print(`Hostname: ${space}`, 'info'); this.terminal.print(`Platform: ${version} on ${platform}`, 'info'); this.terminal.print(`User Agent: ${ua}`, 'info'); } // ── whoami ── _cmdWhoami() { // Try to get from user profile let name = 'darkbot'; try { const prof = this.terminal.moduleManager.modules['WebX11AIAssistantModule']; if (prof && prof._userProfile && prof._userProfile.knownFacts && prof._userProfile.knownFacts.name) { name = prof._userProfile.knownFacts.name; } } catch (e) {} this.terminal.print(name, 'info'); } // ── basename ── _cmdBasename(args) { if (!args[0]) return this.terminal.print('Usage: basename ', 'warning'); const parts = args[0].split('/').filter(p => p); const result = parts.pop() || '/'; this.terminal.print(result, 'info'); } // ── dirname ── _cmdDirname(args) { if (!args[0]) return this.terminal.print('Usage: dirname ', 'warning'); const parts = args[0].split('/').filter(p => p); if (parts.length <= 1) { this.terminal.print('.', 'info'); return; } parts.pop(); const result = args[0].startsWith('/') ? '/' + parts.join('/') : parts.join('/'); this.terminal.print(result || '.', 'info'); } // ── sleep ── _cmdSleep(args) { if (!args[0]) return this.terminal.print('Usage: sleep ', 'warning'); const secs = parseInt(args[0]); if (isNaN(secs) || secs < 1) return this.terminal.print('sleep: invalid duration', 'error'); const max = 30; const actual = Math.min(secs, max); if (actual !== secs) this.terminal.print(`sleep: capped to ${max}s (from ${secs}s)`, 'warning'); this.terminal.print(`Sleeping ${actual}s...`, 'info'); let remaining = actual; const interval = setInterval(() => { remaining--; if (remaining > 0) { this.terminal.print(`${remaining}s...`, 'info'); } else { clearInterval(interval); this.terminal.print('Done', 'success'); } }, 1000); } // ── expand ── _cmdExpand(args) { if (!args[0]) return this.terminal.print('Usage: expand ', 'warning'); const content = this.terminal.fs.readFile(args[0]); if (content === null) return this.terminal.print(`expand: ${args[0]}: No such file`, 'error'); const expanded = content.replace(/\t/g, ' '); this.terminal.print(expanded, 'info'); } } if (typeof window.webx11 !== 'undefined' && window.webx11) { webx11.moduleManager.registerModule(new BusyBoxModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/busybox.js'); }