mirror of
https://github.com/litruv/lit.ruv.wtf.git
synced 2026-07-24 02:36:02 +10:00
Add terminal commands for enhanced functionality
- Implemented 'about' command to provide information about the terminal. - Created 'banner' command to display a welcome message based on terminal width. - Added 'bluesky' command to fetch and display recent posts from Bluesky. - Introduced 'clear' command to clear the terminal screen. - Developed 'color' command to change the terminal's color scheme. - Added 'contact' command to display contact information. - Implemented 'date' command to show the current date and time. - Created 'echo' command to echo back user messages. - Added 'github' command to simulate opening the GitHub repository. - Implemented 'help' command to list available commands. - Developed 'history' command to show command history. - Added 'privacy' command to display the privacy policy. - Implemented 'whoami' command to show user information.
This commit is contained in:
@@ -70,6 +70,6 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.9.0/lib/xterm-addon-web-links.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.9.0/lib/xterm-addon-web-links.min.js"></script>
|
||||||
<script src="boot.js"></script>
|
<script src="boot.js"></script>
|
||||||
<script src="terminal.js"></script>
|
<script type="module" src="terminal.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
56
website/logos/LogoFull.svg
Normal file
56
website/logos/LogoFull.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 77 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 329 KiB After Width: | Height: | Size: 268 KiB |
25
website/scripts/commands/about.js
Normal file
25
website/scripts/commands/about.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* About command - Information about this terminal
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'About this terminal',
|
||||||
|
execute: (term, writeClickable, VERSION) => {
|
||||||
|
return [
|
||||||
|
'',
|
||||||
|
'╔════════════════════════════════════════════════════════════╗',
|
||||||
|
'║ LIT.RUV.WTF TERMINAL ║',
|
||||||
|
'╚════════════════════════════════════════════════════════════╝',
|
||||||
|
'',
|
||||||
|
' A classic terminal interface built with xterm.js',
|
||||||
|
' Features: Keyboard navigation, Mouse support, CRT effects',
|
||||||
|
' Version: ' + VERSION,
|
||||||
|
' Built: ' + new Date().getFullYear(),
|
||||||
|
'',
|
||||||
|
' Technologies:',
|
||||||
|
' • xterm.js - Terminal emulator',
|
||||||
|
' • JavaScript - Terminal logic',
|
||||||
|
' • CSS3 - Classic CRT styling',
|
||||||
|
''
|
||||||
|
].join('\r\n');
|
||||||
|
}
|
||||||
|
};
|
||||||
24
website/scripts/commands/banner.js
Normal file
24
website/scripts/commands/banner.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Banner command - Display welcome banner
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Display welcome banner',
|
||||||
|
execute: (term, writeClickable, VERSION, args, commandHistory, welcomeBannerFull, welcomeBannerCompact, welcomeBannerMinimal) => {
|
||||||
|
const cols = term.cols;
|
||||||
|
if (cols >= 78) {
|
||||||
|
term.writeln(welcomeBannerFull.split('\r\n').slice(0, -3).join('\r\n'));
|
||||||
|
writeClickable(' Type [command=help] for available commands.');
|
||||||
|
term.writeln(' Use ↑/↓ arrows to navigate command history.');
|
||||||
|
term.writeln('');
|
||||||
|
} else if (cols >= 40) {
|
||||||
|
term.writeln(welcomeBannerCompact.split('\r\n').slice(0, -2).join('\r\n'));
|
||||||
|
writeClickable(' Welcome! Type [command=help] for commands.');
|
||||||
|
term.writeln('');
|
||||||
|
} else {
|
||||||
|
term.writeln(welcomeBannerMinimal.split('\r\n').slice(0, -2).join('\r\n'));
|
||||||
|
writeClickable(' Type [command=help]');
|
||||||
|
term.writeln('');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
79
website/scripts/commands/bluesky.js
Normal file
79
website/scripts/commands/bluesky.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Bluesky command - Fetch recent posts from Bluesky
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Fetch recent posts from Bluesky',
|
||||||
|
execute: async (term, writeClickable, VERSION, args) => {
|
||||||
|
const actor = 'lit.mates.dev';
|
||||||
|
const limit = args[0] ? parseInt(args[0]) : 5;
|
||||||
|
|
||||||
|
try {
|
||||||
|
term.writeln('\r\n Fetching posts from Bluesky...\r\n');
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=${actor}&limit=${limit}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return ' Error: Unable to fetch Bluesky posts';
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!data.feed || data.feed.length === 0) {
|
||||||
|
return ' No posts found.';
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = [' ╔════════════════════════════════════════════════════╗'];
|
||||||
|
output.push(' ║ BLUESKY POSTS - @lit.mates.dev ║');
|
||||||
|
output.push(' ╚════════════════════════════════════════════════════╝');
|
||||||
|
output.push('');
|
||||||
|
|
||||||
|
// Reverse to show oldest first, latest at bottom
|
||||||
|
const reversedFeed = [...data.feed].reverse();
|
||||||
|
|
||||||
|
reversedFeed.forEach((item, idx) => {
|
||||||
|
const post = item.post;
|
||||||
|
const text = post.record.text;
|
||||||
|
const createdAt = new Date(post.record.createdAt);
|
||||||
|
const date = createdAt.toLocaleDateString();
|
||||||
|
const time = createdAt.toLocaleTimeString('en-US', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract post ID from URI (at://did:plc:xxx/app.bsky.feed.post/{postId})
|
||||||
|
const postId = post.uri.split('/').pop();
|
||||||
|
const postUrl = `https://bsky.app/profile/${actor}/post/${postId}`;
|
||||||
|
|
||||||
|
output.push(` [${idx + 1}] ${date} ${time}`);
|
||||||
|
output.push(` ${postUrl}`);
|
||||||
|
output.push(' ────────────────────────────────────────');
|
||||||
|
|
||||||
|
// Wrap text to max 50 chars
|
||||||
|
const words = text.split(' ');
|
||||||
|
let line = ' ';
|
||||||
|
words.forEach(word => {
|
||||||
|
if (line.length + word.length + 1 > 52) {
|
||||||
|
output.push(line);
|
||||||
|
line = ' ' + word;
|
||||||
|
} else {
|
||||||
|
line += (line.length > 2 ? ' ' : '') + word;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (line.length > 2) output.push(line);
|
||||||
|
|
||||||
|
output.push('');
|
||||||
|
output.push(` ♡ ${post.likeCount || 0} ↻ ${post.repostCount || 0} 💬 ${post.replyCount || 0}`);
|
||||||
|
output.push('');
|
||||||
|
});
|
||||||
|
|
||||||
|
output.push(` Usage: bluesky [count] (default: 5, max: 20)`);
|
||||||
|
output.push('');
|
||||||
|
|
||||||
|
return output.join('\r\n');
|
||||||
|
} catch (error) {
|
||||||
|
return ' Error: Failed to connect to Bluesky API';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
10
website/scripts/commands/clear.js
Normal file
10
website/scripts/commands/clear.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Clear command - Clear terminal screen
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Clear terminal screen',
|
||||||
|
execute: (term) => {
|
||||||
|
term.clear();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
38
website/scripts/commands/color.js
Normal file
38
website/scripts/commands/color.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Color command - Change color scheme
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Change color scheme',
|
||||||
|
execute: (term, writeClickable, VERSION, args) => {
|
||||||
|
const scheme = args[0] || '';
|
||||||
|
const schemes = {
|
||||||
|
green: { bg: '#001800', fg: '#00ff00', border: '#0f0' },
|
||||||
|
amber: { bg: '#1a0f00', fg: '#ffb000', border: '#ffb000' },
|
||||||
|
blue: { bg: '#000818', fg: '#00a0ff', border: '#00a0ff' },
|
||||||
|
white: { bg: '#0a0a0a', fg: '#e0e0e0', border: '#999' }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!scheme || !schemes[scheme]) {
|
||||||
|
return [
|
||||||
|
'',
|
||||||
|
' Available color schemes:',
|
||||||
|
' • green - Classic green terminal',
|
||||||
|
' • amber - Amber monochrome',
|
||||||
|
' • blue - IBM blue',
|
||||||
|
' • white - White phosphor',
|
||||||
|
'',
|
||||||
|
' Usage: color [scheme]',
|
||||||
|
''
|
||||||
|
].join('\r\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
const colors = schemes[scheme];
|
||||||
|
term.options.theme.background = colors.bg;
|
||||||
|
term.options.theme.foreground = colors.fg;
|
||||||
|
document.querySelector('.container').style.borderColor = colors.border;
|
||||||
|
document.querySelector('.container').style.background = colors.bg;
|
||||||
|
document.body.style.color = colors.fg;
|
||||||
|
|
||||||
|
return `\r\n Color scheme changed to: ${scheme}\r\n`;
|
||||||
|
}
|
||||||
|
};
|
||||||
16
website/scripts/commands/contact.js
Normal file
16
website/scripts/commands/contact.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Contact command - Contact information
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Contact information',
|
||||||
|
execute: () => {
|
||||||
|
return [
|
||||||
|
'',
|
||||||
|
' Contact Information:',
|
||||||
|
' ────────────────────',
|
||||||
|
' Email: contact@lit.ruv.wtf',
|
||||||
|
' Web: https://lit.ruv.wtf',
|
||||||
|
''
|
||||||
|
].join('\r\n');
|
||||||
|
}
|
||||||
|
};
|
||||||
20
website/scripts/commands/date.js
Normal file
20
website/scripts/commands/date.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Date command - Display current date and time
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Display current date and time',
|
||||||
|
execute: () => {
|
||||||
|
const now = new Date();
|
||||||
|
return [
|
||||||
|
'',
|
||||||
|
' ' + now.toLocaleDateString('en-US', {
|
||||||
|
weekday: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
}),
|
||||||
|
' ' + now.toLocaleTimeString('en-US'),
|
||||||
|
''
|
||||||
|
].join('\r\n');
|
||||||
|
}
|
||||||
|
};
|
||||||
9
website/scripts/commands/echo.js
Normal file
9
website/scripts/commands/echo.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Echo command - Echo back message
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Echo back message',
|
||||||
|
execute: (term, writeClickable, VERSION, args) => {
|
||||||
|
return args.join(' ') || '';
|
||||||
|
}
|
||||||
|
};
|
||||||
9
website/scripts/commands/github.js
Normal file
9
website/scripts/commands/github.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* GitHub command - Open GitHub repository
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Open GitHub repository',
|
||||||
|
execute: () => {
|
||||||
|
return '\r\n Opening GitHub...\r\n (This would open your repository URL)\r\n';
|
||||||
|
}
|
||||||
|
};
|
||||||
32
website/scripts/commands/help.js
Normal file
32
website/scripts/commands/help.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Help command - Display available commands
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Display available commands',
|
||||||
|
execute: (term, writeClickable) => {
|
||||||
|
term.writeln('');
|
||||||
|
term.writeln('╔════════════════════════════════════════════════════════════╗');
|
||||||
|
term.writeln('║ AVAILABLE COMMANDS ║');
|
||||||
|
term.writeln('╚════════════════════════════════════════════════════════════╝');
|
||||||
|
term.writeln('');
|
||||||
|
writeClickable(' [command=help] - Display this help message');
|
||||||
|
writeClickable(' [command=about] - Information about this terminal');
|
||||||
|
writeClickable(' [command=clear] - Clear the terminal screen');
|
||||||
|
term.writeln(' echo - Echo back your message (usage: echo [message])');
|
||||||
|
writeClickable(' [command=date] - Display current date and time');
|
||||||
|
writeClickable(' [command=whoami] - Display current user information');
|
||||||
|
writeClickable(' [command=history] - Show command history');
|
||||||
|
writeClickable(' [command=color] - Change terminal color scheme');
|
||||||
|
writeClickable(' [command=banner] - Display welcome banner');
|
||||||
|
writeClickable(' [command=bluesky] - Fetch recent posts from Bluesky');
|
||||||
|
writeClickable(' [command=chat] - Enter interactive chat (type /quit to exit)');
|
||||||
|
writeClickable(' [command=github] - Visit GitHub repository');
|
||||||
|
writeClickable(' [command=contact] - Display contact information');
|
||||||
|
writeClickable(' [command=privacy] - Display privacy policy');
|
||||||
|
term.writeln('');
|
||||||
|
term.writeln('Navigate: Use ↑/↓ arrows for command history');
|
||||||
|
term.writeln('Mouse: Click commands to run them');
|
||||||
|
term.writeln('');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
17
website/scripts/commands/history.js
Normal file
17
website/scripts/commands/history.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* History command - Show command history
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Show command history',
|
||||||
|
execute: (term, writeClickable, VERSION, args, commandHistory) => {
|
||||||
|
if (commandHistory.length === 0) {
|
||||||
|
return '\r\n No command history yet.\r\n';
|
||||||
|
}
|
||||||
|
let output = ['\r\n Command History:', ' ───────────────'];
|
||||||
|
commandHistory.forEach((cmd, idx) => {
|
||||||
|
output.push(` ${(idx + 1).toString().padStart(3, ' ')} ${cmd}`);
|
||||||
|
});
|
||||||
|
output.push('');
|
||||||
|
return output.join('\r\n');
|
||||||
|
}
|
||||||
|
};
|
||||||
36
website/scripts/commands/privacy.js
Normal file
36
website/scripts/commands/privacy.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Privacy command - Privacy policy
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Privacy policy',
|
||||||
|
execute: () => {
|
||||||
|
return [
|
||||||
|
'',
|
||||||
|
'╔════════════════════════════════════════════════════════════╗',
|
||||||
|
'║ PRIVACY POLICY ║',
|
||||||
|
'╚════════════════════════════════════════════════════════════╝',
|
||||||
|
'',
|
||||||
|
' Data Collection:',
|
||||||
|
' ────────────────',
|
||||||
|
' • This terminal uses localStorage to save your chat session',
|
||||||
|
' • Chat messages are stored on our Matrix homeserver',
|
||||||
|
' • No cookies or tracking scripts are used',
|
||||||
|
' • No analytics or third-party tracking',
|
||||||
|
'',
|
||||||
|
' Matrix Chat:',
|
||||||
|
' ────────────',
|
||||||
|
' • Chat credentials stored locally in your browser',
|
||||||
|
' • Messages sent through Matrix protocol (b.ruv.wtf)',
|
||||||
|
' • Use "chat disconnect" to clear stored credentials',
|
||||||
|
'',
|
||||||
|
' Your Rights:',
|
||||||
|
' ────────────',
|
||||||
|
' • Clear localStorage anytime via browser settings',
|
||||||
|
' • Request data deletion: contact@lit.ruv.wtf',
|
||||||
|
' • All code is open source and auditable',
|
||||||
|
'',
|
||||||
|
' Updates: Privacy policy last updated March 2026',
|
||||||
|
''
|
||||||
|
].join('\r\n');
|
||||||
|
}
|
||||||
|
};
|
||||||
15
website/scripts/commands/whoami.js
Normal file
15
website/scripts/commands/whoami.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Whoami command - Display user information
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Display user information',
|
||||||
|
execute: () => {
|
||||||
|
return [
|
||||||
|
'',
|
||||||
|
' User: visitor@lit.ruv.wtf',
|
||||||
|
' Session: ' + Date.now(),
|
||||||
|
' Terminal: xterm.js',
|
||||||
|
''
|
||||||
|
].join('\r\n');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -489,6 +489,29 @@ body.keyboard-open .status-bar {
|
|||||||
padding: 3px 8px;
|
padding: 3px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Small viewport (under 1280x720) - detected via JS */
|
||||||
|
body.small-viewport .crt-border-overlay {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.small-viewport .container,
|
||||||
|
body.small-viewport .boot-screen {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.small-viewport .terminal-header {
|
||||||
|
border-bottom: 1px solid #0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.small-viewport .status-bar {
|
||||||
|
border-top: 1px solid #0f0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Scrollbar styling */
|
/* Scrollbar styling */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
// Import commands
|
||||||
|
import helpCmd from './scripts/commands/help.js';
|
||||||
|
import aboutCmd from './scripts/commands/about.js';
|
||||||
|
import clearCmd from './scripts/commands/clear.js';
|
||||||
|
import echoCmd from './scripts/commands/echo.js';
|
||||||
|
import dateCmd from './scripts/commands/date.js';
|
||||||
|
import whoamiCmd from './scripts/commands/whoami.js';
|
||||||
|
import historyCmd from './scripts/commands/history.js';
|
||||||
|
import colorCmd from './scripts/commands/color.js';
|
||||||
|
import bannerCmd from './scripts/commands/banner.js';
|
||||||
|
import githubCmd from './scripts/commands/github.js';
|
||||||
|
import contactCmd from './scripts/commands/contact.js';
|
||||||
|
import privacyCmd from './scripts/commands/privacy.js';
|
||||||
|
import blueskyCmd from './scripts/commands/bluesky.js';
|
||||||
|
|
||||||
// Version
|
// Version
|
||||||
const VERSION = '1.0.0';
|
const VERSION = '1.0.0';
|
||||||
|
|
||||||
@@ -108,7 +123,9 @@ term.registerLinkProvider({
|
|||||||
// Adjust font size based on screen width
|
// Adjust font size based on screen width
|
||||||
function adjustFontSize() {
|
function adjustFontSize() {
|
||||||
const width = window.innerWidth;
|
const width = window.innerWidth;
|
||||||
if (width < 480) {
|
if (width < 350) {
|
||||||
|
term.options.fontSize = 8;
|
||||||
|
} else if (width < 480) {
|
||||||
term.options.fontSize = 10;
|
term.options.fontSize = 10;
|
||||||
} else if (width < 768) {
|
} else if (width < 768) {
|
||||||
term.options.fontSize = 12;
|
term.options.fontSize = 12;
|
||||||
@@ -120,9 +137,24 @@ function adjustFontSize() {
|
|||||||
|
|
||||||
adjustFontSize();
|
adjustFontSize();
|
||||||
|
|
||||||
|
// Check viewport size for border display
|
||||||
|
function checkViewportSize() {
|
||||||
|
const width = window.innerWidth;
|
||||||
|
const height = window.innerHeight;
|
||||||
|
|
||||||
|
if (width < 1280 || height < 720) {
|
||||||
|
document.body.classList.add('small-viewport');
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('small-viewport');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkViewportSize();
|
||||||
|
|
||||||
// Make terminal responsive
|
// Make terminal responsive
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
adjustFontSize();
|
adjustFontSize();
|
||||||
|
checkViewportSize();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mobile keyboard detection using Visual Viewport API
|
// Mobile keyboard detection using Visual Viewport API
|
||||||
@@ -147,6 +179,7 @@ function checkKeyboardState() {
|
|||||||
if (window.visualViewport) {
|
if (window.visualViewport) {
|
||||||
window.visualViewport.addEventListener('resize', () => {
|
window.visualViewport.addEventListener('resize', () => {
|
||||||
checkKeyboardState();
|
checkKeyboardState();
|
||||||
|
checkViewportSize();
|
||||||
adjustFontSize();
|
adjustFontSize();
|
||||||
setTimeout(positionInlineInput, 50);
|
setTimeout(positionInlineInput, 50);
|
||||||
});
|
});
|
||||||
@@ -156,6 +189,7 @@ if (window.visualViewport) {
|
|||||||
window.addEventListener('orientationchange', () => {
|
window.addEventListener('orientationchange', () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
initialViewportHeight = window.innerHeight;
|
initialViewportHeight = window.innerHeight;
|
||||||
|
checkViewportSize();
|
||||||
}, 300);
|
}, 300);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -306,7 +340,12 @@ async function enterChatMode() {
|
|||||||
term.writeln('');
|
term.writeln('');
|
||||||
term.writeln('─'.repeat(term.cols || 60));
|
term.writeln('─'.repeat(term.cols || 60));
|
||||||
term.write('\x1b[1;32m>\x1b[0m ');
|
term.write('\x1b[1;32m>\x1b[0m ');
|
||||||
showInlineInput();
|
|
||||||
|
// Delay showing input to ensure terminal has rendered and cursor is positioned
|
||||||
|
setTimeout(() => {
|
||||||
|
showInlineInput();
|
||||||
|
}, 50);
|
||||||
|
|
||||||
updateQuickCommands('chat');
|
updateQuickCommands('chat');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,6 +491,12 @@ function renderChatMessage(msg) {
|
|||||||
|
|
||||||
// Redraw prompt with current input
|
// Redraw prompt with current input
|
||||||
term.write(`\x1b[1;32m>\x1b[0m ${chatMode.inputLine}`);
|
term.write(`\x1b[1;32m>\x1b[0m ${chatMode.inputLine}`);
|
||||||
|
|
||||||
|
// Scroll to bottom to show new message
|
||||||
|
term.scrollToBottom();
|
||||||
|
|
||||||
|
// Reposition the inline input after terminal updates
|
||||||
|
setTimeout(() => positionInlineInput(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render chat input prompt (efficiently)
|
// Render chat input prompt (efficiently)
|
||||||
@@ -498,295 +543,56 @@ async function sendChatMessage(message) {
|
|||||||
// Available commands
|
// Available commands
|
||||||
const commands = {
|
const commands = {
|
||||||
help: {
|
help: {
|
||||||
description: 'Display available commands',
|
description: helpCmd.description,
|
||||||
execute: () => {
|
execute: (args) => helpCmd.execute(term, writeClickable, VERSION, args, commandHistory)
|
||||||
term.writeln('');
|
|
||||||
term.writeln('╔════════════════════════════════════════════════════════════╗');
|
|
||||||
term.writeln('║ AVAILABLE COMMANDS ║');
|
|
||||||
term.writeln('╚════════════════════════════════════════════════════════════╝');
|
|
||||||
term.writeln('');
|
|
||||||
writeClickable(' [command=help] - Display this help message');
|
|
||||||
writeClickable(' [command=about] - Information about this terminal');
|
|
||||||
writeClickable(' [command=clear] - Clear the terminal screen');
|
|
||||||
term.writeln(' echo - Echo back your message (usage: echo [message])');
|
|
||||||
writeClickable(' [command=date] - Display current date and time');
|
|
||||||
writeClickable(' [command=whoami] - Display current user information');
|
|
||||||
writeClickable(' [command=history] - Show command history');
|
|
||||||
writeClickable(' [command=color] - Change terminal color scheme');
|
|
||||||
writeClickable(' [command=banner] - Display welcome banner');
|
|
||||||
writeClickable(' [command=bluesky] - Fetch recent posts from Bluesky');
|
|
||||||
writeClickable(' [command=chat] - Enter interactive chat (type /quit to exit)');
|
|
||||||
writeClickable(' [command=github] - Visit GitHub repository');
|
|
||||||
writeClickable(' [command=contact] - Display contact information');
|
|
||||||
writeClickable(' [command=privacy] - Display privacy policy');
|
|
||||||
term.writeln('');
|
|
||||||
term.writeln('Navigate: Use ↑/↓ arrows for command history');
|
|
||||||
term.writeln('Mouse: Click commands to run them');
|
|
||||||
term.writeln('');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
about: {
|
about: {
|
||||||
description: 'About this terminal',
|
description: aboutCmd.description,
|
||||||
execute: () => {
|
execute: (args) => aboutCmd.execute(term, writeClickable, VERSION, args, commandHistory)
|
||||||
return [
|
|
||||||
'',
|
|
||||||
'╔════════════════════════════════════════════════════════════╗',
|
|
||||||
'║ LIT.RUV.WTF TERMINAL ║',
|
|
||||||
'╚════════════════════════════════════════════════════════════╝',
|
|
||||||
'',
|
|
||||||
' A classic terminal interface built with xterm.js',
|
|
||||||
' Features: Keyboard navigation, Mouse support, CRT effects',
|
|
||||||
' Version: ' + VERSION,
|
|
||||||
' Built: ' + new Date().getFullYear(),
|
|
||||||
'',
|
|
||||||
' Technologies:',
|
|
||||||
' • xterm.js - Terminal emulator',
|
|
||||||
' • JavaScript - Terminal logic',
|
|
||||||
' • CSS3 - Classic CRT styling',
|
|
||||||
''
|
|
||||||
].join('\r\n');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
clear: {
|
clear: {
|
||||||
description: 'Clear terminal screen',
|
description: clearCmd.description,
|
||||||
execute: () => {
|
execute: (args) => clearCmd.execute(term, writeClickable, VERSION, args, commandHistory)
|
||||||
term.clear();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
echo: {
|
echo: {
|
||||||
description: 'Echo back message',
|
description: echoCmd.description,
|
||||||
execute: (args) => {
|
execute: (args) => echoCmd.execute(term, writeClickable, VERSION, args, commandHistory)
|
||||||
return args.join(' ') || '';
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
date: {
|
date: {
|
||||||
description: 'Display current date and time',
|
description: dateCmd.description,
|
||||||
execute: () => {
|
execute: (args) => dateCmd.execute(term, writeClickable, VERSION, args, commandHistory)
|
||||||
const now = new Date();
|
|
||||||
return [
|
|
||||||
'',
|
|
||||||
' ' + now.toLocaleDateString('en-US', {
|
|
||||||
weekday: 'long',
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric'
|
|
||||||
}),
|
|
||||||
' ' + now.toLocaleTimeString('en-US'),
|
|
||||||
''
|
|
||||||
].join('\r\n');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
whoami: {
|
whoami: {
|
||||||
description: 'Display user information',
|
description: whoamiCmd.description,
|
||||||
execute: () => {
|
execute: (args) => whoamiCmd.execute(term, writeClickable, VERSION, args, commandHistory)
|
||||||
return [
|
|
||||||
'',
|
|
||||||
' User: visitor@lit.ruv.wtf',
|
|
||||||
' Session: ' + Date.now(),
|
|
||||||
' Terminal: xterm.js',
|
|
||||||
''
|
|
||||||
].join('\r\n');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
history: {
|
history: {
|
||||||
description: 'Show command history',
|
description: historyCmd.description,
|
||||||
execute: () => {
|
execute: (args) => historyCmd.execute(term, writeClickable, VERSION, args, commandHistory)
|
||||||
if (commandHistory.length === 0) {
|
|
||||||
return '\r\n No command history yet.\r\n';
|
|
||||||
}
|
|
||||||
let output = ['\r\n Command History:', ' ───────────────'];
|
|
||||||
commandHistory.forEach((cmd, idx) => {
|
|
||||||
output.push(` ${(idx + 1).toString().padStart(3, ' ')} ${cmd}`);
|
|
||||||
});
|
|
||||||
output.push('');
|
|
||||||
return output.join('\r\n');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
description: 'Change color scheme',
|
description: colorCmd.description,
|
||||||
execute: (args) => {
|
execute: (args) => colorCmd.execute(term, writeClickable, VERSION, args, commandHistory)
|
||||||
const scheme = args[0] || '';
|
|
||||||
const schemes = {
|
|
||||||
green: { bg: '#001800', fg: '#00ff00', border: '#0f0' },
|
|
||||||
amber: { bg: '#1a0f00', fg: '#ffb000', border: '#ffb000' },
|
|
||||||
blue: { bg: '#000818', fg: '#00a0ff', border: '#00a0ff' },
|
|
||||||
white: { bg: '#0a0a0a', fg: '#e0e0e0', border: '#999' }
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!scheme || !schemes[scheme]) {
|
|
||||||
return [
|
|
||||||
'',
|
|
||||||
' Available color schemes:',
|
|
||||||
' • green - Classic green terminal',
|
|
||||||
' • amber - Amber monochrome',
|
|
||||||
' • blue - IBM blue',
|
|
||||||
' • white - White phosphor',
|
|
||||||
'',
|
|
||||||
' Usage: color [scheme]',
|
|
||||||
''
|
|
||||||
].join('\r\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
const colors = schemes[scheme];
|
|
||||||
term.options.theme.background = colors.bg;
|
|
||||||
term.options.theme.foreground = colors.fg;
|
|
||||||
document.querySelector('.container').style.borderColor = colors.border;
|
|
||||||
document.querySelector('.container').style.background = colors.bg;
|
|
||||||
document.body.style.color = colors.fg;
|
|
||||||
|
|
||||||
return `\r\n Color scheme changed to: ${scheme}\r\n`;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
banner: {
|
banner: {
|
||||||
description: 'Display welcome banner',
|
description: bannerCmd.description,
|
||||||
execute: () => {
|
execute: (args) => bannerCmd.execute(term, writeClickable, VERSION, args, commandHistory, welcomeBannerFull, welcomeBannerCompact, welcomeBannerMinimal)
|
||||||
const cols = term.cols;
|
|
||||||
if (cols >= 78) {
|
|
||||||
term.writeln(welcomeBannerFull.split('\r\n').slice(0, -3).join('\r\n'));
|
|
||||||
writeClickable(' Type [command=help] for available commands.');
|
|
||||||
term.writeln(' Use ↑/↓ arrows to navigate command history.');
|
|
||||||
term.writeln('');
|
|
||||||
} else if (cols >= 40) {
|
|
||||||
term.writeln(welcomeBannerCompact.split('\r\n').slice(0, -2).join('\r\n'));
|
|
||||||
writeClickable(' Welcome! Type [command=help] for commands.');
|
|
||||||
term.writeln('');
|
|
||||||
} else {
|
|
||||||
term.writeln(welcomeBannerMinimal.split('\r\n').slice(0, -2).join('\r\n'));
|
|
||||||
writeClickable(' Type [command=help]');
|
|
||||||
term.writeln('');
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
github: {
|
github: {
|
||||||
description: 'Open GitHub repository',
|
description: githubCmd.description,
|
||||||
execute: () => {
|
execute: (args) => githubCmd.execute(term, writeClickable, VERSION, args, commandHistory)
|
||||||
return '\r\n Opening GitHub...\r\n (This would open your repository URL)\r\n';
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
contact: {
|
contact: {
|
||||||
description: 'Contact information',
|
description: contactCmd.description,
|
||||||
execute: () => {
|
execute: (args) => contactCmd.execute(term, writeClickable, VERSION, args, commandHistory)
|
||||||
return [
|
|
||||||
'',
|
|
||||||
' Contact Information:',
|
|
||||||
' ────────────────────',
|
|
||||||
' Email: contact@lit.ruv.wtf',
|
|
||||||
' Web: https://lit.ruv.wtf',
|
|
||||||
''
|
|
||||||
].join('\r\n');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
privacy: {
|
privacy: {
|
||||||
description: 'Privacy policy',
|
description: privacyCmd.description,
|
||||||
execute: () => {
|
execute: (args) => privacyCmd.execute(term, writeClickable, VERSION, args, commandHistory)
|
||||||
return [
|
|
||||||
'',
|
|
||||||
'╔════════════════════════════════════════════════════════════╗',
|
|
||||||
'║ PRIVACY POLICY ║',
|
|
||||||
'╚════════════════════════════════════════════════════════════╝',
|
|
||||||
'',
|
|
||||||
' Data Collection:',
|
|
||||||
' ────────────────',
|
|
||||||
' • This terminal uses localStorage to save your chat session',
|
|
||||||
' • Chat messages are stored on our Matrix homeserver',
|
|
||||||
' • No cookies or tracking scripts are used',
|
|
||||||
' • No analytics or third-party tracking',
|
|
||||||
'',
|
|
||||||
' Matrix Chat:',
|
|
||||||
' ────────────',
|
|
||||||
' • Chat credentials stored locally in your browser',
|
|
||||||
' • Messages sent through Matrix protocol (b.ruv.wtf)',
|
|
||||||
' • Use "chat disconnect" to clear stored credentials',
|
|
||||||
'',
|
|
||||||
' Your Rights:',
|
|
||||||
' ────────────',
|
|
||||||
' • Clear localStorage anytime via browser settings',
|
|
||||||
' • Request data deletion: contact@lit.ruv.wtf',
|
|
||||||
' • All code is open source and auditable',
|
|
||||||
'',
|
|
||||||
' Updates: Privacy policy last updated March 2026',
|
|
||||||
''
|
|
||||||
].join('\r\n');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
bluesky: {
|
bluesky: {
|
||||||
description: 'Fetch recent posts from Bluesky',
|
description: blueskyCmd.description,
|
||||||
execute: async (args) => {
|
execute: async (args) => await blueskyCmd.execute(term, writeClickable, VERSION, args, commandHistory)
|
||||||
const actor = 'lit.mates.dev';
|
|
||||||
const limit = args[0] ? parseInt(args[0]) : 5;
|
|
||||||
|
|
||||||
try {
|
|
||||||
term.writeln('\r\n Fetching posts from Bluesky...\r\n');
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=${actor}&limit=${limit}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return ' Error: Unable to fetch Bluesky posts';
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (!data.feed || data.feed.length === 0) {
|
|
||||||
return ' No posts found.';
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = [' ╔════════════════════════════════════════════════════╗'];
|
|
||||||
output.push(' ║ BLUESKY POSTS - @lit.mates.dev ║');
|
|
||||||
output.push(' ╚════════════════════════════════════════════════════╝');
|
|
||||||
output.push('');
|
|
||||||
|
|
||||||
// Reverse to show oldest first, latest at bottom
|
|
||||||
const reversedFeed = [...data.feed].reverse();
|
|
||||||
|
|
||||||
reversedFeed.forEach((item, idx) => {
|
|
||||||
const post = item.post;
|
|
||||||
const text = post.record.text;
|
|
||||||
const createdAt = new Date(post.record.createdAt);
|
|
||||||
const date = createdAt.toLocaleDateString();
|
|
||||||
const time = createdAt.toLocaleTimeString('en-US', {
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Extract post ID from URI (at://did:plc:xxx/app.bsky.feed.post/{postId})
|
|
||||||
const postId = post.uri.split('/').pop();
|
|
||||||
const postUrl = `https://bsky.app/profile/${actor}/post/${postId}`;
|
|
||||||
|
|
||||||
output.push(` [${idx + 1}] ${date} ${time}`);
|
|
||||||
output.push(` ${postUrl}`);
|
|
||||||
output.push(' ────────────────────────────────────────');
|
|
||||||
|
|
||||||
// Wrap text to max 50 chars
|
|
||||||
const words = text.split(' ');
|
|
||||||
let line = ' ';
|
|
||||||
words.forEach(word => {
|
|
||||||
if (line.length + word.length + 1 > 52) {
|
|
||||||
output.push(line);
|
|
||||||
line = ' ' + word;
|
|
||||||
} else {
|
|
||||||
line += (line.length > 2 ? ' ' : '') + word;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (line.length > 2) output.push(line);
|
|
||||||
|
|
||||||
output.push('');
|
|
||||||
output.push(` ♡ ${post.likeCount || 0} ↻ ${post.repostCount || 0} 💬 ${post.replyCount || 0}`);
|
|
||||||
output.push('');
|
|
||||||
});
|
|
||||||
|
|
||||||
output.push(` Usage: bluesky [count] (default: 5, max: 20)`);
|
|
||||||
output.push('');
|
|
||||||
|
|
||||||
return output.join('\r\n');
|
|
||||||
} catch (error) {
|
|
||||||
return ' Error: Failed to connect to Bluesky API';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
chat: {
|
chat: {
|
||||||
description: 'Connect to chat room',
|
description: 'Connect to chat room',
|
||||||
@@ -1039,7 +845,12 @@ const welcomeBannerMinimal = [
|
|||||||
// Get appropriate banner based on terminal width
|
// Get appropriate banner based on terminal width
|
||||||
function getWelcomeBanner() {
|
function getWelcomeBanner() {
|
||||||
const cols = term.cols;
|
const cols = term.cols;
|
||||||
if (cols >= 78) {
|
const width = window.innerWidth;
|
||||||
|
|
||||||
|
// Use minimal mode for very narrow screens
|
||||||
|
if (width < 350 || cols < 30) {
|
||||||
|
return welcomeBannerMinimal;
|
||||||
|
} else if (cols >= 78) {
|
||||||
return welcomeBannerFull;
|
return welcomeBannerFull;
|
||||||
} else if (cols >= 40) {
|
} else if (cols >= 40) {
|
||||||
return welcomeBannerCompact;
|
return welcomeBannerCompact;
|
||||||
@@ -1117,10 +928,10 @@ async function submitInlineInput() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write what user typed
|
|
||||||
term.write(rawValue + '\r\n');
|
|
||||||
|
|
||||||
if (cmd === '/help') {
|
if (cmd === '/help') {
|
||||||
|
// Move cursor up to separator, clear it and the prompt
|
||||||
|
term.write('\x1b[1A\x1b[2K\r');
|
||||||
|
|
||||||
term.writeln('\x1b[33mChat Commands:\x1b[0m');
|
term.writeln('\x1b[33mChat Commands:\x1b[0m');
|
||||||
term.writeln(' /help - Show this help message');
|
term.writeln(' /help - Show this help message');
|
||||||
term.writeln(' /nick [name] - Change your display name');
|
term.writeln(' /nick [name] - Change your display name');
|
||||||
@@ -1133,6 +944,10 @@ async function submitInlineInput() {
|
|||||||
|
|
||||||
if (cmd.startsWith('/nick ')) {
|
if (cmd.startsWith('/nick ')) {
|
||||||
const newNick = cmd.substring(6).trim();
|
const newNick = cmd.substring(6).trim();
|
||||||
|
|
||||||
|
// Move cursor up to separator, clear it
|
||||||
|
term.write('\x1b[1A\x1b[2K\r');
|
||||||
|
|
||||||
if (!newNick) {
|
if (!newNick) {
|
||||||
term.writeln('\x1b[31mError: /nick [name]\x1b[0m');
|
term.writeln('\x1b[31mError: /nick [name]\x1b[0m');
|
||||||
} else {
|
} else {
|
||||||
@@ -1171,15 +986,18 @@ async function submitInlineInput() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cmd && !cmd.startsWith('/')) {
|
if (cmd && !cmd.startsWith('/')) {
|
||||||
|
// Don't write the message here - let sync handle it
|
||||||
await sendChatMessage(cmd);
|
await sendChatMessage(cmd);
|
||||||
setTimeout(() => positionInlineInput(), 10);
|
return;
|
||||||
} else if (cmd.startsWith('/')) {
|
} else if (cmd.startsWith('/')) {
|
||||||
|
// Move cursor up to separator, clear it
|
||||||
|
term.write('\x1b[1A\x1b[2K\r');
|
||||||
term.writeln(`\x1b[31mUnknown command: ${cmd.split(' ')[0]}. Type /help\x1b[0m`);
|
term.writeln(`\x1b[31mUnknown command: ${cmd.split(' ')[0]}. Type /help\x1b[0m`);
|
||||||
term.writeln('─'.repeat(term.cols || 60));
|
term.writeln('─'.repeat(term.cols || 60));
|
||||||
term.write('\x1b[1;32m>\x1b[0m ');
|
term.write('\x1b[1;32m>\x1b[0m ');
|
||||||
setTimeout(() => positionInlineInput(), 10);
|
setTimeout(() => positionInlineInput(), 10);
|
||||||
} else {
|
} else {
|
||||||
term.write('\x1b[1;32m>\x1b[0m ');
|
// Empty message, just reposition the prompt
|
||||||
setTimeout(() => positionInlineInput(), 10);
|
setTimeout(() => positionInlineInput(), 10);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user