How the CyberStrikeAI Web Console Delivers an Interactive Experience

The CyberStrikeAI web console achieves its interactive, desktop-like experience through a vanilla JavaScript SPA architecture that combines hash-based routing, WebSocket terminal sessions, Server-Sent Event streaming for AI responses, and dynamic UI components like @-mention tool pickers and draft persistence.

The CyberStrikeAI project (Ed1s0nZ/CyberStrikeAI) provides a browser-based security operations interface built as a single-page application (SPA). This architecture eliminates full page reloads while enabling real-time terminal access, progressive AI chat rendering, and dynamic tool selection directly within the browser.

Hash-Based SPA Routing for Instant Navigation

The console’s navigation system resides in web/static/js/router.js and orchestrates page transitions through URL hash manipulation. When the document fires DOMContentLoaded, the initRouter() function (lines 5-38) parses window.location.hash to extract page identifiers and query parameters.

The switchPage(pageId) method hides all .page sections, activates the target pane (#page-${pageId}), and delegates to page-specific initializers like initChatPage() or initTerminal(). Because navigation relies solely on hash changes, the browser never triggers HTTP requests for new pages, creating an instantaneous transition feel that mimics native desktop software.

Real-Time Terminal via WebSocket

The terminal component creates an interactive shell inside the browser using a bidirectional WebSocket connection defined in web/static/js/terminal.js. The buildTerminalWSURL() function (lines 75-84) dynamically constructs the WebSocket URL based on the current protocol—using wss:// for HTTPS and ws:// for HTTP—while appending authentication tokens from localStorage:

function buildTerminalWSURL() {
    const proto = (window.location.protocol === 'https:') ? 'wss://' : 'ws://';
    let url = proto + window.location.host + '/api/terminal/ws';
    const token = getStoredAuthToken();
    if (token) url += '?token=' + encodeURIComponent(token);
    return url;
}

On the server side, internal/handler/terminal_ws_unix.go spawns the requested shell process, streams stdout/stderr through the WebSocket, and enforces timeouts via terminalTimeout. Client-side, incoming frames are written directly to an xterm.js instance using term.write(), providing a fully functional terminal emulator within the web interface.

Streaming AI Output with Server-Sent Events

The chat interface in web/static/js/chat.js consumes AI-generated content through HTTP streaming rather than waiting for complete responses. The client initiates a fetch request to /api/agent-loop/stream and processes the response body as a ReadableStream using response.body.getReader().

The backend returns Server-Sent Events (SSE) where each line begins with data: followed by JSON payloads. The client loop (around lines 191-225) concatenates partial chunks, splits on newlines, and parses each event through handleStreamEvent(). This architecture supports:

  • Real-time progress bars via addProgressMessage()
  • MCP execution tracking with clickable "调用 #n" buttons that trigger showMCPDetail(execId)
  • Graceful cancellation when the server aborts long-running tool calls

@-Mention Tool Picker for Dynamic Tool Selection

When users type "@" in the chat input, handleChatInputInput activates a mention state that triggers updateMentionStateFromInput. The system lazily loads tool metadata through ensureMentionToolsLoaded(), which calls fetchMentionTools() to paginate through /api/config/tools (page size 100).

The implementation merges internal and external MCP tools, deduplicates them using composite keys like external_mcp::tool.name, and filters by the roleEnabled flag to highlight tools available for the current user role. The renderMentionSuggestions() function (lines 551-581) generates a dropdown of <button> elements with keyboard navigation (arrow keys, Enter/Tab) and injects selected tool names directly into the textarea.

File Upload and Draft Persistence

The console protects user work through intelligent draft management and supports rich file attachments. The setupChatFileUpload() function in chat.js handles both hidden file inputs and drag-and-drop interfaces, reading files via FileReader as either text or base64 data URLs depending on MIME type.

Draft persistence operates through debounced localStorage writes:

  • saveChatDraftDebounced() stores textarea content under the key cyberstrike-chat-draft
  • restoreChatDraft() retrieves content on page initialization only if the input is empty and no recent message was sent
  • clearChatDraft() removes stored data after successful message transmission

This ensures users never lose unsent work while preventing the accidental restoration of placeholder text.

Responsive Sidebar and Navigation State

The sidebar component provides collapsible navigation with state persistence across sessions. The toggleSidebar() function saves the collapsed state to localStorage under sidebarCollapsed, while updateNavState() automatically expands sub-menus (MCP, Knowledge, Skills, Roles) when their child pages become active.

When collapsed, clicking top-level navigation items triggers showSubmenuPopup() to display floating menu overlays, maintaining full navigation capabilities even in compact mode. All state transitions are handled within router.js (lines 75-126 and 166-200) without server round-trips.

Summary

  • Hash-based routing in web/static/js/router.js enables instant page transitions without HTTP reloads by manipulating window.location.hash and switching visibility of .page sections.
  • WebSocket terminal integration provides a true interactive shell through web/static/js/terminal.js and the Go handler internal/handler/terminal_ws_unix.go, supporting both ws:// and wss:// protocols with token authentication.
  • SSE streaming delivers progressive AI output via fetch() and ReadableStream processing in web/static/js/chat.js, enabling real-time progress bars and execution status updates.
  • @-mention system offers dynamic tool selection with role-aware filtering, pagination, and keyboard navigation through functions like ensureMentionToolsLoaded() and renderMentionSuggestions().
  • Draft persistence safeguards user input using debounced localStorage operations (saveChatDraftDebounced(), restoreChatDraft()), while file upload handlers support drag-and-drop attachment with automatic MIME-type detection.
  • Responsive sidebar maintains navigation state in localStorage and adapts its presentation between expanded and collapsed modes without page refreshes.

Frequently Asked Questions

How does the CyberStrikeAI console handle page navigation without reloading?

The console implements a hash-based router in web/static/js/router.js that intercepts all navigation through window.location.hash changes. The initRouter() function parses hash values to identify pages, calls switchPage() to toggle visibility of HTML sections, and invokes page-specific initializers like initChatPage(). Because the browser remains on the same document, transitions occur instantly without network latency or rendering delays.

What technology enables the interactive terminal inside the browser?

The terminal uses native WebSocket connections managed by web/static/js/terminal.js and the Go backend internal/handler/terminal_ws_unix.go. The client constructs protocol-aware URLs (wss:// or ws://) with authentication tokens, then streams user input through ws.send() while writing server responses into an xterm.js terminal instance. The server spawns actual shell processes and pipes their stdout/stderr through the WebSocket frame by frame.

How does the chat interface stream AI responses in real-time?

The chat module consumes Server-Sent Events (SSE) from the /api/agent-loop/stream endpoint using the Streams API. The client reads the response body through a ReadableStream reader, decodes chunks with TextDecoder, and parses lines beginning with data: as JSON events. The handleStreamEvent() function processes these increments to update progress bars, append text to message bubbles, and display MCP execution IDs as they arrive from the backend.

Can the console restore unsent messages if the browser is refreshed?

Yes, the implementation in web/static/js/chat.js automatically persists chat input using saveChatDraftDebounced(), which writes to localStorage under the key cyberstrike-chat-draft after a debounce delay. On initialization, restoreChatDraft() retrieves this content only if the textarea is empty and no message was recently sent, preventing the accidental display of stale data while protecting unsent work across browser sessions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →