Commit 89f4bad14f5c63ffd28c6860f99798733f4f37a1

Commits
[COMMIT BEGIN]
commit 89f4bad14f5c63ffd28c6860f99798733f4f37a1
Author: 0x4248 <[email protected]>
Date:   Sun Jan 11 23:42:22 2026 +0000

    Orion: added manual, and many improvements
    
    Signed-off-by: 0x4248 <[email protected]>

diff --git a/lab/orion/commands/echo.py b/lab/orion/commands/echo.py
index 2c3874e..7abfd7e 100644
--- a/lab/orion/commands/echo.py
+++ b/lab/orion/commands/echo.py
@@ -2,6 +2,7 @@ from fastapi import Request
 from core.registry import registry
 from core.commands import Command
 from core import page
+import manpages.echo
 
 def echo(request: Request, text: str = ""):
     if not text:
diff --git a/lab/orion/commands/system/manual.py b/lab/orion/commands/system/manual.py
new file mode 100644
index 0000000..7fbe5f0
--- /dev/null
+++ b/lab/orion/commands/system/manual.py
@@ -0,0 +1,66 @@
+# Manual command man <name/alias> or ? <name/alias> or help <name/alias>
+from fastapi import APIRouter, Request
+from fastapi.responses import RedirectResponse
+from core.registry import registry
+from core.commands import Command
+from core import page
+
+from core.manual import manual_registry
+from core.commands import Command
+
+router = APIRouter()
[email protected]("/man/{name}")
+def manual_page_api(request: Request, name: str):
+    return manual_page(request, name)
+
+def manual_page(request: Request, name: str = ""):
+    if name == "":
+        # show forum
+        return RedirectResponse(url="/forum/man")
+    man = manual_registry.get(name)
+    if not man:
+        return page.message(
+            request,
+            "MANUAL",
+            error=f"No manual page for: {name}"
+        )
+    
+    return page.static(request, f"MANUAL PAGE FOR: {man.name.upper()}", html=f"""
+    <h1 style="text-align: center; text-transform: uppercase;">{man.name}</h1>
+<pre>{man.text}</pre>
+
+<a href="javascript:history.back()" style="text-align:center">[ BACK ]</a>
+                       """, buttons=page.with_nav(page.DEFAULT_NAV))
+
+registry.register(Command(
+    name="man",
+    handler=manual_page,
+    summary="Show manual page for command",
+    mode="both",
+    form_fields=[
+        {"name": "name", "type": "text"},
+    ],
+))
+
+### ALIASES ###
+
+registry.register(Command(
+    name="?",
+    handler=manual_page,
+    summary="Show manual page for command",
+    mode="both",
+    form_fields=[
+        {"name": "name", "type": "text"},
+    ],
+))
+
+
+registry.register(Command(
+    name="help",
+    handler=manual_page,
+    summary="Show manual page for command",
+    mode="both",
+    form_fields=[
+        {"name": "name", "type": "text"},
+    ],
+))
diff --git a/lab/orion/core/layout.py b/lab/orion/core/layout.py
index 38f8de1..083bb2b 100644
--- a/lab/orion/core/layout.py
+++ b/lab/orion/core/layout.py
@@ -127,6 +127,21 @@ input[type=submit]:focus , input[type=submit]:hover {
 SCRIPT = """
 <script>
 document.addEventListener('keydown', function(event) {
+    if (event.key === 'Escape') {
+        document.activeElement.blur();
+    }
+
+    if (event.key === 'F1') {
+        const firstInput = document.querySelector('input, textarea');
+        if (firstInput) {
+            const commandName = firstInput.value.trim();
+            if (commandName) {
+                window.location.href = '/man/' + encodeURIComponent(commandName);
+            }
+        }
+    }
+
+
     if (event.key === 'F2') {
         window.location.href = '/command';
     }
@@ -134,10 +149,10 @@ document.addEventListener('keydown', function(event) {
     if (event.target.tagName.toLowerCase() === 'input' || event.target.tagName.toLowerCase() === 'textarea') {
         return;
     }
-    if (event.key === 'F4' || event.key === ',' || event.key === 'Escape') {
+    if (event.key === 'F4' || event.key === ',' || event.key === 'Backspace') {
         window.history.back();
     }
-    if (event.key === 'F1') {
+    if (event.key === '.') {
         const firstInput = document.querySelector('input, textarea');
         if (firstInput) {
             firstInput.focus();
@@ -161,7 +176,10 @@ def layout(request: Request, title: str, buttons: list[tuple[str, str]], header_
     header = header_html if header_html is not None else f"<div class='header'><strong>ORION SYSTEM:</strong> {title}<br>{nav}</div>"
     return f"""
     <html>
-      <head>{STYLE}</head>
+      <head>
+      <title>ORION SYSTEM: {title}</title>
+      {STYLE}
+      </head>
       <body>
         {header}
         <hr/>
diff --git a/lab/orion/core/manual.py b/lab/orion/core/manual.py
new file mode 100644
index 0000000..6845748
--- /dev/null
+++ b/lab/orion/core/manual.py
@@ -0,0 +1,34 @@
+from typing import Dict, Optional, List
+
+# Manual funcions
+# Commands can create manual pages by doing manual.register(NAME, TEXT, ALIASES (List, optional))
+
+class ManualPage:
+    def __init__(self, name: str, text: str, aliases: Optional[List[str]] = None):
+        self.name = name
+        self.text = text
+        self.aliases = aliases if aliases is not None else []
+class ManualRegistry:
+    def __init__(self):
+        self._manuals: Dict[str, ManualPage] = {}
+
+    def register(self, name: str, text: str, aliases: Optional[List[str]] = None):
+        if name in self._manuals:
+            raise ValueError(f"Duplicate manual page: {name}")
+        manual_page = ManualPage(name, text, aliases)
+        self._manuals[name] = manual_page
+        if aliases:
+            for alias in aliases:
+                if alias in self._manuals:
+                    raise ValueError(f"Duplicate manual page alias: {alias}")
+                self._manuals[alias] = manual_page
+
+    def get(self, name: str) -> Optional[ManualPage]:
+        return self._manuals.get(name)
+
+    def all(self) -> List[ManualPage]:
+        # Return only unique manual pages (avoid duplicates from aliases)
+        unique_manuals = {mp.name: mp for mp in self._manuals.values()}
+        return list(unique_manuals.values())
+    
+manual_registry = ManualRegistry()
diff --git a/lab/orion/main.py b/lab/orion/main.py
index 3ed3e08..53d82db 100644
--- a/lab/orion/main.py
+++ b/lab/orion/main.py
@@ -1,13 +1,14 @@
 from fastapi import FastAPI, Request
-from fastapi.responses import RedirectResponse
+from fastapi.responses import FileResponse, RedirectResponse
 
-from pages import command
+from pages import command,about
 from core import auth
 from core import page as p
 
 
 import commands.system.open
-# import commands.testing.demo
+
+import commands.system.manual as manual
 import commands.echo
 
 app = FastAPI()
@@ -18,7 +19,8 @@ app.middleware("http")(auth.auth_middleware)
 
 app.include_router(auth.router)
 app.include_router(command.router)
-
+app.include_router(about.router)
+app.include_router(manual.router)
 
 @app.exception_handler(404)
 async def not_found(request: Request, exc):
@@ -29,6 +31,9 @@ async def not_found(request: Request, exc):
         "<a href='javascript:history.back()'>[ BACK ]</a>",
     )
 
[email protected]("/favicon.ico")
+async def favicon():
+    return FileResponse("./static/mascot.ico")
 
 if __name__ == "__main__":
     import uvicorn
diff --git a/lab/orion/manpages/echo.py b/lab/orion/manpages/echo.py
new file mode 100644
index 0000000..8758ada
--- /dev/null
+++ b/lab/orion/manpages/echo.py
@@ -0,0 +1,19 @@
+from core import manual
+
+manual.manual_registry.register(
+    name="echo",
+    text="""
+DESCRIPTION:
+    Echo back the provided text.
+
+USAGE:
+    echo <text>
+
+EXAMPLE:
+    $ echo Hello, World!
+    Hello, World!
+
+ALIASES:
+    none
+"""
+)
\ No newline at end of file
[COMMIT END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.