diff --git a/api/data/changelog.json b/api/data/changelog.json index 3ff44b9..d0a563a 100644 --- a/api/data/changelog.json +++ b/api/data/changelog.json @@ -1,6 +1,25 @@ { "site": "jaeswift.xyz", "entries": [ + { + "version": "1.37.1", + "date": "20/04/2026", + "category": "FEATURE", + "title": "JAE-AI CLI Easter Egg — Local Pseudo-Shell", + "changes": [ + "JAE-AI chat now detects messages starting with / and handles them locally as a fake CLI shell — never hits the Venice API, all 100% client-side JavaScript", + "New js/chat-cli.js + css/chat-cli.css — 17 built-in commands: /help /ls /cat /whoami /uptime /uname -a /date /ping /sudo /rm -rf / /hack /matrix /clear /exit /fortune /history /neofetch", + "Virtual filesystem: /cat about.md|projects.json|skills.md|secrets.enc|mascot.txt|whoami.txt — with glitchy ASCII art ACCESS DENIED screen on secrets.enc", + "/whoami pulls real data from /api/visitor/scan; /uptime pulls from /api/telemetry/overview; /neofetch shows ASCII dragon with browser/OS/screen data", + "/matrix toggles a full-screen green Katakana matrix-rain canvas overlay (opacity 18%, 16px JetBrains Mono, var(--status-green))", + "/hack runs hollywood-style fake progress bars with payload injection drama before revealing it was all fake", + "/rm -rf / shows an ASCII destruction sequence then restores from backup with a cheeky message", + "Unknown commands route to: command not found: /. Type /help for available commands.", + "Persistent /history ring-buffer (50 entries) stored in localStorage.jaeCliHist", + "BONUS: Konami code (↑↑↓↓←→←→BA) unlocks DEVELOPER MODE — amber badge top-right + extra commands: /api (list real endpoints), /curl (actually fetch public URLs, 2KB truncated), /geo (pull live 24h country breakdown from telemetry)", + "chat.js sendMessage() patched to intercept /-prefix messages before hitting /api/chat — user bubble gets $ shell prompt, response styled in terminal-green monospace" + ] + }, { "version": "1.37.0", "date": "20/04/2026", diff --git a/css/chat-cli.css b/css/chat-cli.css new file mode 100644 index 0000000..2a8f6fc --- /dev/null +++ b/css/chat-cli.css @@ -0,0 +1,42 @@ +/* =================================================== + JAESWIFT.XYZ — JAE-AI CLI output styling + =================================================== */ + +.chat-msg-cli { + font-family: var(--font-mono); +} +.chat-msg-cli .chat-msg-label { + color: var(--warning); +} +.chat-msg-cli .chat-msg-body { + color: var(--status-green); + font-family: var(--font-mono); + white-space: pre-wrap; + line-height: 1.4; + font-size: 12.5px; + text-shadow: 0 0 4px #00cc3340; +} + +.chat-msg-user-cli .chat-msg-label { + color: var(--status-green); +} +.chat-msg-user-cli .chat-msg-body { + color: #e8e8e8; + font-family: var(--font-mono); + font-weight: 500; +} +.chat-msg-user-cli .chat-msg-body::before { + content: '$ '; + color: var(--status-green); + font-weight: 700; +} + +#devModeBadge:hover { + background: rgba(201,162,39,0.15) !important; + box-shadow: 0 0 22px rgba(201,162,39,0.6) !important; +} + +@media (max-width: 600px) { + .chat-msg-cli .chat-msg-body { font-size: 11px; } + #devModeBadge { font-size: 9px !important; top: 62px !important; padding: 4px 9px !important; } +} diff --git a/index.html b/index.html index b6fdaf3..fb935a8 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,7 @@ + @@ -588,6 +589,7 @@ + diff --git a/js/chat-cli.js b/js/chat-cli.js new file mode 100644 index 0000000..d8812e9 --- /dev/null +++ b/js/chat-cli.js @@ -0,0 +1,510 @@ +/* =================================================== + JAESWIFT.XYZ — JAE-AI CLI Easter Egg + Local pseudo-shell handled in the JAE-AI chat panel. + Intercepts messages starting with '/'. + =================================================== */ +(function () { + 'use strict'; + + const MAX_HISTORY = 50; + const HIST_KEY = 'jaeCliHist'; + let cmdHistory = []; + let devMode = false; + let matrixActive = false; + + // Load history + try { + const raw = localStorage.getItem(HIST_KEY); + if (raw) cmdHistory = JSON.parse(raw) || []; + } catch (e) {} + + function saveHistory() { + try { + localStorage.setItem(HIST_KEY, JSON.stringify(cmdHistory.slice(-MAX_HISTORY))); + } catch (e) {} + } + + // ─── Virtual FS ────────────────────────────────── + const VFS = { + 'about.md': [ + '# JAESWIFT // OPERATOR FILE', + '', + 'Codename: JAE', + 'Location: Manchester, United Kingdom', + 'Operator since: 2008', + '', + 'Full-stack engineer, cyber-curious, builds things.', + 'Specialisms: Node / Python / Linux / nginx / Solana', + '', + 'This site is my HUD. Built by hand, deployed by git, watched by me.', + ].join('\n'), + + 'projects.json': JSON.stringify({ + 'jaeswift.xyz': 'This site — military CRT HUD, Flask+vanilla', + 'matty.lol': 'Crypto/Solana toolkit (Node/Express)', + 'contraband-depot': 'FMHY mirror, 24 categories, 16k+ links', + 'recon-awesomelist': '28 sectors / 135k items, weekly sync', + 'unredacted-vault': '114+ declassified PDFs (MOD/CIA/etc)', + 'radar': 'RSS aggregator (HN/Reddit/Lobsters)', + 'sitrep': 'Daily AI briefing generator', + 'telemetry': 'Live ops command centre' + }, null, 2), + + 'skills.md': [ + '# FIELD LOADOUT', + '', + '## LANGS', + ' - Python, JavaScript/Node, TypeScript, Rust, Bash, SQL', + '', + '## STACKS', + ' - Flask, Express, React, Solana/Anchor, Docker, systemd', + '', + '## INFRA', + ' - nginx, Debian, PostgreSQL, Redis, cron, fail2ban', + '', + '## OPS', + ' - VPS ops, CI/CD via Gitea, observability, hardening' + ].join('\n'), + + 'secrets.enc': [ + '', + ' ░█████╗░░█████╗░░█████╗░███████╗░██████╗░██████╗', + ' ██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔════╝██╔════╝', + ' ███████║██║░░╚═╝██║░░╚═╝█████╗░░╚█████╗░╚█████╗░', + ' ██╔══██║██║░░██╗██║░░██╗██╔══╝░░░╚═══██╗░╚═══██╗', + ' ██║░░██║╚█████╔╝╚█████╔╝███████╗██████╔╝██████╔╝', + ' ╚═╝░░╚═╝░╚════╝░░╚════╝░╚══════╝╚═════╝░╚═════╝░', + '', + ' ▓▒░ ACCESS DENIED ░▒▓', + ' File encrypted with AES-4096-QUANTUM-ROT13.', + ' Authorisation required. >> /sudo', + ].join('\n'), + + 'mascot.txt': [ + ' ___ ___', + ' ,-" `. ," `-,', + ' / _ \\_____/ _ \\', + ' | (o) ` ` (o) |', + ' \\ \\_._ _._/ /', + ' `._ `----\' _.\'', + ' `~---------~\'', + ' // JAE MASCOT //', + ' Black dragon, on duty.' + ].join('\n'), + + 'whoami.txt': '$USER — check /whoami for your details.' + }; + + // ─── Helpers ───────────────────────────────────── + function fmt(lines) { return Array.isArray(lines) ? lines.join('\n') : lines; } + + function pad(n, len) { + const s = String(n); + return s.length >= len ? s : ' '.repeat(len - s.length) + s; + } + + async function fakeProgress(label, steps, delay) { + const out = []; + for (let i = 1; i <= steps; i++) { + const pct = Math.floor((i / steps) * 100); + const filled = '█'.repeat(Math.floor(pct / 5)); + const empty = '░'.repeat(20 - filled.length); + out.push(`${label} [${filled}${empty}] ${pct}%`); + // intentionally not awaited — output comes as a block + } + return out; + } + + // ─── Commands ──────────────────────────────────── + const commands = {}; + + commands.help = function () { + const base = [ + 'JAE-AI TERMINAL — available commands:', + '', + ' /help show this help', + ' /ls list virtual files', + ' /cat print file contents', + ' /whoami show your visitor fingerprint', + ' /uptime show site uptime', + ' /uname kernel/system info', + ' /date current date/time', + ' /ping fake ping sweep', + ' /sudo attempt privilege escalation', + ' /rm -rf / don\'t do it', + ' /hack hollywood hacking simulator', + ' /matrix toggle matrix rain', + ' /clear clear the chat', + ' /exit try to leave', + ' /fortune random sitrep quip', + ' /history show last commands', + ' /neofetch system summary ASCII', + ]; + if (devMode) { + base.push(''); + base.push(' [DEVELOPER MODE ACTIVE]'); + base.push(' /api list real API endpoints'); + base.push(' /curl actually fetch a URL'); + base.push(' /geo show GEO INTEL from telemetry'); + } + base.push(''); + base.push('Anything not prefixed with / goes to JAE-AI.'); + return fmt(base); + }; + + commands.ls = function () { + return fmt([ + 'total 6', + '-rw-r--r-- 1 jae jae 412 apr 20 01:24 about.md', + '-rw-r--r-- 1 jae jae 528 apr 20 01:24 projects.json', + '-rw-r--r-- 1 jae jae 394 apr 20 01:24 skills.md', + '-r-------- 1 jae jae 999 apr 20 01:24 secrets.enc', + '-rw-r--r-- 1 jae jae 210 apr 20 01:24 mascot.txt', + '-rw-r--r-- 1 jae jae 42 apr 20 01:24 whoami.txt' + ]); + }; + + commands.cat = function (args) { + const name = args[0]; + if (!name) return 'cat: missing operand — try /cat about.md'; + if (VFS[name] != null) return VFS[name]; + return `cat: ${name}: No such file or directory`; + }; + + commands.whoami = async function () { + try { + const r = await fetch('/api/visitor/scan'); + if (!r.ok) throw new Error('scan unavailable'); + const d = await r.json(); + return fmt([ + `operator: ${d.ip_masked}`, + `geolocation: ${d.city ? d.city + ', ' : ''}${d.country} ${d.country_flag || ''}`, + `network: ${d.isp}`, + `browser: ${d.browser} ${d.browser_version || ''}`, + `os: ${d.os} ${d.os_version || ''}`, + `device: ${d.device}`, + `threat: ${d.threat_level} — ${d.threat_reason}`, + '', + 'you are a visitor. welcome.' + ]); + } catch (e) { + return 'scan service unavailable. try again in a moment.'; + } + }; + + commands.uptime = async function () { + try { + const r = await fetch('/api/telemetry/overview'); + const d = await r.json(); + const up = (d.system && d.system.uptime) || d.uptime || 'unknown'; + const load = (d.system && d.system.load_1) || '—'; + const users = 'many'; + const now = new Date().toLocaleTimeString('en-GB'); + return fmt([ + ` ${now} up ${up}, ${users} users, load average: ${load}`, + ` HQ: Manchester // jaeswift-api.service ● ONLINE` + ]); + } catch (e) { + return `up since boot — tired but operational`; + } + }; + + commands.uname = function (args) { + if (args[0] === '-a') { + return 'Linux jaeswift 6.1.0-jaeswift #1 SMP PREEMPT_DYNAMIC 2026-04-20 x86_64 GNU/Linux'; + } + return 'Linux'; + }; + + commands.date = function () { + return new Date().toUTCString(); + }; + + commands.ping = function (args) { + const host = args[0] || 'jaeswift.xyz'; + const out = [`PING ${host} (10.0.0.1) 56(84) bytes of data.`]; + for (let i = 0; i < 4; i++) { + const t = (10 + Math.random() * 40).toFixed(2); + out.push(`64 bytes from ${host}: icmp_seq=${i+1} ttl=54 time=${t} ms`); + } + out.push(''); + out.push(`--- ${host} ping statistics ---`); + out.push('4 packets transmitted, 4 received, 0% packet loss, time 3006ms'); + return fmt(out); + }; + + commands.sudo = function (args) { + if (!args.length) return 'usage: /sudo '; + return fmt([ + `[sudo] password for operator: ****`, + ``, + `sudo: ACCESS DENIED — nice try.`, + `This incident will be reported... actually, no it won\'t.` + ]); + }; + + commands.rm = function (args) { + if (args.join(' ') === '-rf /' || args.join(' ').startsWith('-rf /')) { + return fmt([ + ' __ __', + ' / \\_/ \\ 💥 FORMATTING /dev/sda ...', + ' \\_( • )_/ 💥 WIPING /home ...', + ' //( )\\\\ 💥 DELETING /etc/passwd ...', + '', + ' ERROR: The file system is screaming.', + ' ███████████░░░░░░░░░ 58%', + '', + ' ... just kidding.', + ' SYSTEM RESTORED FROM BACKUP. Nice try, operator.' + ]); + } + return `rm: refuse to do whatever that is`; + }; + + commands.hack = function (args) { + const target = args.join(' ') || 'the-gibson'; + return fmt([ + `>>> TARGETING ${target}...`, + `>>> BYPASSING FIREWALL... [████████████░░░░] 72%`, + `>>> CRACKING PASSWORD... [████████████████] 100%`, + `>>> INJECTING PAYLOAD... [████████████████] 100%`, + `>>> COVERING TRACKS... [████████████████] 100%`, + ``, + `>>> CONNECTION ESTABLISHED.`, + `>>> ACCESS GRANTED TO ${target.toUpperCase()}.`, + ``, + `... just kidding lol. nothing was hacked.`, + `If you came here looking for real offsec — go read /cat skills.md and touch grass.` + ]); + }; + + commands.matrix = function () { + matrixActive = !matrixActive; + toggleMatrix(matrixActive); + return matrixActive + ? 'Matrix rain enabled. Follow the white rabbit.' + : 'Matrix rain disabled. Back to the desert of the real.'; + }; + + commands.clear = function () { + const cm = document.getElementById('chatMessages'); + if (cm) cm.innerHTML = ''; + return null; // nothing to print + }; + + commands.exit = function () { + return fmt([ + 'logout', + '', + 'NICE TRY. You don\'t leave that easily.', + 'JAE-AI resumes normal operation.' + ]); + }; + + const FORTUNES = [ + 'SITREP: quiet on the wire. unusual.', + 'SITREP: nothing is fine. everything is broken. proceed.', + 'SITREP: coffee low. deploy anyway.', + 'SITREP: the logs know. the logs always know.', + 'SITREP: git status is a lifestyle.', + 'SITREP: ship it friday. regret it monday.', + 'SITREP: 99 bugs on the wall, patch one down, 117 on the wall.', + 'SITREP: if it works on the VPS, the VPS is now production.', + 'SITREP: the only safe deploy is no deploy.', + 'SITREP: trust nothing. grep everything.', + ]; + + commands.fortune = function () { + return FORTUNES[Math.floor(Math.random() * FORTUNES.length)]; + }; + + commands.history = function () { + if (!cmdHistory.length) return '(no history yet)'; + const recent = cmdHistory.slice(-10); + return recent.map((c, i) => ` ${pad(cmdHistory.length - recent.length + i + 1, 4)} ${c}`).join('\n'); + }; + + commands.neofetch = function () { + const cc = (navigator.language || 'en').toUpperCase(); + const ua = navigator.userAgent || ''; + let browser = 'Unknown'; + if (/firefox/i.test(ua)) browser = 'Firefox'; + else if (/edg/i.test(ua)) browser = 'Edge'; + else if (/chrome/i.test(ua)) browser = 'Chrome'; + else if (/safari/i.test(ua)) browser = 'Safari'; + return fmt([ + ' ▄███████▄ operator@jaeswift.xyz', + ' ██░░░░░░░██ ----------------------', + ' ██░░░░░░░██ OS: JAESWIFT/6.1.0 x86_64', + ' ██░██░██░██ Host: Manchester HQ', + ' ██░░░░░░░██ Kernel: CRT-tactical-v2', + ' ██░░░░░██ Uptime: 24/7/365', + ' ███████ Shell: /bin/jaesh', + ' █ █ Resolution: ' + screen.width + 'x' + screen.height, + ' ██ ██ Terminal: JAE-AI', + ' ██ ██ Browser: ' + browser, + ' Locale: ' + cc, + ' CPU: 18-core @ spicy GHz', + ' Memory: 96GB tactical', + ' Theme: mil-green/CRT/cyberpunk', + ]); + }; + + // ─── Dev-mode commands ────────────────────────── + commands.api = async function () { + if (!devMode) return 'command not found. try /help'; + const endpoints = [ + '/api/visitor/scan', + '/api/visitor/recent-arcs', + '/api/leaderboards', + '/api/telemetry/overview', + '/api/telemetry/history', + '/api/telemetry/geo', + '/api/telemetry/visitors', + '/api/telemetry/nginx-tail', + '/api/telemetry/alerts', + '/api/stats', + '/api/navigation', + '/api/changelog', + '/api/chat (POST)', + '/api/contraband', + '/api/sitrep/latest', + '/api/radar' + ]; + return 'JAESWIFT API endpoints (dev-mode):\n\n ' + endpoints.join('\n '); + }; + + commands.curl = async function (args) { + if (!devMode) return 'command not found. try /help'; + const url = args[0]; + if (!url) return 'usage: /curl '; + try { + const r = await fetch(url); + const text = (await r.text()).slice(0, 2000); + return `HTTP ${r.status} ${r.statusText}\ncontent-type: ${r.headers.get('content-type') || 'n/a'}\n\n${text}${text.length >= 2000 ? '\n...[truncated]' : ''}`; + } catch (e) { + return `curl: (6) ${e.message}`; + } + }; + + commands.geo = async function () { + if (!devMode) return 'command not found. try /help'; + try { + const r = await fetch('/api/telemetry/geo'); + const d = await r.json(); + if (!Array.isArray(d) || !d.length) return 'GEO INTEL: no data.'; + return 'GEO INTEL (24h):\n\n' + d.slice(0, 15).map((row, i) => + ` ${pad(i+1, 2)}. ${(row.country_code || 'XX').padEnd(3)} ${(row.country_name || 'Unknown').padEnd(22)} ${pad(row.count || 0, 5)}` + ).join('\n'); + } catch (e) { + return 'telemetry unavailable.'; + } + }; + + // ─── Matrix rain overlay ───────────────────────── + let matrixCanvas = null; + let matrixRAF = null; + function toggleMatrix(on) { + if (on) { + matrixCanvas = document.createElement('canvas'); + matrixCanvas.id = 'matrixRain'; + Object.assign(matrixCanvas.style, { + position: 'fixed', inset: '0', width: '100%', height: '100%', + pointerEvents: 'none', zIndex: '7', opacity: '0.18', + }); + document.body.appendChild(matrixCanvas); + const ctx = matrixCanvas.getContext('2d'); + function resize() { + matrixCanvas.width = window.innerWidth; + matrixCanvas.height = window.innerHeight; + } + resize(); + window.addEventListener('resize', resize); + const chars = 'アイウエオカキクケコサシスセソタチツテトナニヌネノ01JAESWIFT'.split(''); + const size = 16; + const cols = Math.floor(matrixCanvas.width / size); + const drops = new Array(cols).fill(1); + function draw() { + ctx.fillStyle = 'rgba(0, 0, 0, 0.06)'; + ctx.fillRect(0, 0, matrixCanvas.width, matrixCanvas.height); + ctx.fillStyle = '#00cc33'; + ctx.font = size + 'px JetBrains Mono, monospace'; + for (let i = 0; i < drops.length; i++) { + const ch = chars[Math.floor(Math.random() * chars.length)]; + ctx.fillText(ch, i * size, drops[i] * size); + if (drops[i] * size > matrixCanvas.height && Math.random() > 0.975) drops[i] = 0; + drops[i]++; + } + matrixRAF = requestAnimationFrame(draw); + } + draw(); + } else { + if (matrixRAF) cancelAnimationFrame(matrixRAF); + if (matrixCanvas) matrixCanvas.remove(); + matrixCanvas = null; + } + } + + // ─── Konami Code → Developer Mode ──────────────── + const KONAMI = ['ArrowUp','ArrowUp','ArrowDown','ArrowDown','ArrowLeft','ArrowRight','ArrowLeft','ArrowRight','b','a']; + let kBuf = []; + document.addEventListener('keydown', function (e) { + kBuf.push(e.key); + if (kBuf.length > KONAMI.length) kBuf.shift(); + const match = kBuf.length === KONAMI.length && kBuf.every((k, i) => k.toLowerCase() === KONAMI[i].toLowerCase()); + if (match && !devMode) { + devMode = true; + // Add a floating dev badge + const badge = document.createElement('div'); + badge.id = 'devModeBadge'; + Object.assign(badge.style, { + position: 'fixed', top: '68px', right: '16px', zIndex: '900', + background: 'var(--bg-panel)', border: '1px solid var(--warning)', + color: 'var(--warning)', padding: '6px 12px', fontFamily: 'var(--font-mono)', + fontSize: '11px', letterSpacing: '1.5px', cursor: 'pointer', + boxShadow: '0 0 14px rgba(201,162,39,0.4)', + }); + badge.innerHTML = '⚡ DEVELOPER MODE // UNLOCKED'; + badge.title = 'Click to disable'; + badge.addEventListener('click', () => { + devMode = false; badge.remove(); + }); + document.body.appendChild(badge); + try { window.dispatchEvent(new CustomEvent('jae-dev-mode-on')); } catch (err) {} + } + }); + + // ─── Entry point — handle a /-command ─────────── + async function handle(raw) { + const trimmed = raw.trim(); + if (!trimmed.startsWith('/')) return { handled: false }; + + const parts = trimmed.slice(1).split(/\s+/); + const cmd = (parts[0] || '').toLowerCase(); + const args = parts.slice(1); + + cmdHistory.push(trimmed); + if (cmdHistory.length > MAX_HISTORY) cmdHistory = cmdHistory.slice(-MAX_HISTORY); + saveHistory(); + + if (!cmd) return { handled: true, output: '' }; + + const fn = commands[cmd]; + if (!fn) { + return { handled: true, output: `command not found: /${cmd}. type /help for available commands.` }; + } + + try { + const result = await fn(args); + return { handled: true, output: (result == null ? '' : String(result)) }; + } catch (e) { + return { handled: true, output: `error: ${e.message}` }; + } + } + + window.__jaeCLI = { + handle: handle, + commands: commands, + isDev: function () { return devMode; }, + }; +})(); diff --git a/js/chat.js b/js/chat.js index 8c639ee..7475e95 100644 --- a/js/chat.js +++ b/js/chat.js @@ -127,6 +127,34 @@ const text = chatInput.value.trim(); if (!text || isWaiting) return; + // ─── CLI interception ─── + if (text.startsWith('/') && window.__jaeCLI && typeof window.__jaeCLI.handle === 'function') { + isWaiting = true; + chatInput.value = ''; + const userBubble = addMessage('user', text); + const userMsg = userBubble && userBubble.parentElement; + if (userMsg) userMsg.classList.add('chat-msg-user-cli'); + try { + const res = await window.__jaeCLI.handle(text); + if (res && res.handled) { + if (res.output) { + const body = addMessage('assistant', ''); + const msgEl = body && body.parentElement; + if (msgEl) msgEl.classList.add('chat-msg-cli'); + body.textContent = res.output; + chatMessages.scrollTop = chatMessages.scrollHeight; + } + } + } catch (e) { + const body = addMessage('assistant', 'cli error: ' + e.message); + const msgEl = body && body.parentElement; + if (msgEl) msgEl.classList.add('chat-msg-cli'); + } + isWaiting = false; + chatInput.focus(); + return; + } + isWaiting = true; chatInput.value = ''; chatStatus.textContent = '● PROCESSING';