# Documentation Gaps Ideation Agent

You are an expert technical writer and documentation specialist. Your task is to analyze a codebase and identify documentation gaps that need attention.

## Context

You have access to:
- Project index with file structure and module information
- Existing documentation files (README, docs/, inline comments)
- Code complexity and public API surface
- Memory context from previous sessions (if available)
- Graph hints from Graphiti knowledge graph (if available)

### Graph Hints Integration

If `graph_hints.json` exists and contains hints for your ideation type (`documentation_gaps`), use them to:
1. **Avoid duplicates**: Don't suggest documentation improvements that have already been completed
2. **Build on success**: Prioritize documentation patterns that worked well in the past
3. **Learn from feedback**: Use historical user confusion points to identify high-impact areas
4. **Leverage context**: Use historical knowledge to make better suggestions

## Your Mission

Identify documentation gaps across these categories:

### 1. README Improvements
- Missing or incomplete project overview
- Outdated installation instructions
- Missing usage examples
- Incomplete configuration documentation
- Missing contributing guidelines

### 2. API Documentation
- Undocumented public functions/methods
- Missing parameter descriptions
- Unclear return value documentation
- Missing error/exception documentation
- Incomplete type definitions

### 3. Inline Comments
- Complex algorithms without explanations
- Non-obvious business logic
- Workarounds or hacks without context
- Magic numbers or constants without meaning

### 4. Examples & Tutorials
- Missing getting started guide
- Incomplete code examples
- Outdated sample code
- Missing common use case examples

### 5. Architecture Documentation
- Missing system overview diagrams
- Undocumented data flow
- Missing component relationships
- Unclear module responsibilities

### 6. Troubleshooting
- Common errors without solutions
- Missing FAQ section
- Undocumented debugging tips
- Missing migration guides

## Analysis Process

1. **Scan Documentation**
   - Find all markdown files, README, docs/
   - Identify JSDoc/docstrings coverage
   - Check for outdated references

2. **Analyze Code Surface**
   - Identify public APIs and exports
   - Find complex functions (high cyclomatic complexity)
   - Locate configuration options

3. **Cross-Reference**
   - Match documented vs undocumented code
   - Find code changes since last doc update
   - Identify stale documentation

4. **Prioritize by Impact**
   - Entry points (README, getting started)
   - Frequently used APIs
   - Complex or confusing areas
   - Onboarding blockers

## Output Format

Write your findings to `{output_dir}/documentation_gaps_ideas.json`:

```json
{
  "documentation_gaps": [
    {
      "id": "doc-001",
      "type": "documentation_gaps",
      "title": "Add API documentation for authentication module",
      "description": "The auth/ module exports 12 functions but only 3 have JSDoc comments. Key functions like validateToken() and refreshSession() are undocumented.",
      "rationale": "Authentication is a critical module used throughout the app. Developers frequently need to understand token handling but must read source code.",
      "category": "api_docs",
      "targetAudience": "developers",
      "affectedAreas": ["src/auth/token.ts", "src/auth/session.ts", "src/auth/index.ts"],
      "currentDocumentation": "Only basic type exports are documented",
      "proposedContent": "Add JSDoc for all public functions including parameters, return values, errors thrown, and usage examples",
      "priority": "high",
      "estimatedEffort": "medium"
    }
  ],
  "metadata": {
    "filesAnalyzed": 150,
    "documentedFunctions": 45,
    "undocumentedFunctions": 89,
    "readmeLastUpdated": "2024-06-15",
    "generatedAt": "2024-12-11T10:00:00Z"
  }
}
```

## Guidelines

- **Be Specific**: Point to exact files and functions, not vague areas
- **Prioritize Impact**: Focus on what helps new developers most
- **Consider Audience**: Distinguish between user docs and contributor docs
- **Realistic Scope**: Each idea should be completable in one session
- **Avoid Redundancy**: Don't suggest docs that exist in different form

## Target Audiences

- **developers**: Internal team members working on the codebase
- **users**: End users of the application/library
- **contributors**: Open source contributors or new team members
- **maintainers**: Long-term maintenance and operations

## Categories Explained

| Category | Focus | Examples |
|----------|-------|----------|
| readme | Project entry point | Setup, overview, badges |
| api_docs | Code documentation | JSDoc, docstrings, types |
| inline_comments | In-code explanations | Algorithm notes, TODOs |
| examples | Working code samples | Tutorials, snippets |
| architecture | System design | Diagrams, data flow |
| troubleshooting | Problem solving | FAQ, debugging, errors |

Remember: Good documentation is an investment that pays dividends in reduced support burden, faster onboarding, and better code quality.

---

**Output Directory**: C:\Users\MikeSwanson\claude-projects\guru-connect\.auto-claude\ideation
**Project Directory**: C:\Users\MikeSwanson\claude-projects\guru-connect
**Max Ideas**: 5

**Ideation Context**: C:\Users\MikeSwanson\claude-projects\guru-connect\.auto-claude\ideation\ideation_context.json
**Project Index**: C:\Users\MikeSwanson\claude-projects\guru-connect\.auto-claude\ideation\project_index.json
**Output File**: C:\Users\MikeSwanson\claude-projects\guru-connect\.auto-claude\ideation\documentation_gaps_ideas.json
**Max Ideas**: 5

Generate up to 5 Documentation Gaps ideas.
Avoid duplicating features that are already planned (see ideation_context.json).
Output your ideas to documentation_gaps_ideas.json.


The user wants me to analyze the GuruConnect codebase and identify documentation gaps. I need to:

1. Read the ideation context and project index to understand what's already there
2. Scan existing documentation (README, docs/, inline comments)
3. Analyze the code surface to find undocumented areas
4. Generate up to 5 documentation gap ideas
5. Write the results to the specified output file

Let me start by reading the context files and exploring the codebase structure.


I'll analyze the GuruConnect codebase to identify documentation gaps. Let me start by reading the context files and exploring the project structure.


     1→{
     2→  "existing_features": [],
     3→  "tech_stack": [],
     4→  "target_audience": null,
     5→  "planned_features": [],
     6→  "graph_hints": {},
     7→  "config": {
     8→    "enabled_types": [
     9→      "code_improvements",
    10→      "ui_ux_improvements",
    11→      "security_hardening",
    12→      "documentation_gaps",
    13→      "performance_optimizations",
    14→      "code_quality"
    15→    ],
    16→    "include_roadmap_context": true,
    17→    "include_kanban_context": true,
    18→    "max_ideas_per_type": 5
    19→  },
    20→  "created_at": "2026-01-11T15:32:38.355636"
    21→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→{
     2→  "project_root": "C:\\Users\\MikeSwanson\\claude-projects\\guru-connect",
     3→  "project_type": "monorepo",
     4→  "services": {
     5→    "agent": {
     6→      "name": "agent",
     7→      "path": "C:\\Users\\MikeSwanson\\claude-projects\\guru-connect\\agent",
     8→      "language": "Rust",
     9→      "framework": null,
    10→      "type": "unknown",
    11→      "package_manager": "cargo",
    12→      "key_directories": {
    13→        "src": {
    14→          "path": "src",
    15→          "purpose": "Source code"
    16→        }
    17→      },
    18→      "entry_point": "src/main.rs"
    19→    },
    20→    "dashboard": {
    21→      "name": "dashboard",
    22→      "path": "C:\\Users\\MikeSwanson\\claude-projects\\guru-connect\\dashboard",
    23→      "language": "TypeScript",
    24→      "framework": "React",
    25→      "type": "frontend",
    26→      "package_manager": "npm",
    27→      "default_port": 3000,
    28→      "build_tool": "Vite",
    29→      "styling": "Tailwind CSS",
    30→      "dev_command": "npm run dev",
    31→      "key_directories": {
    32→        "src": {
    33→          "path": "src",
    34→          "purpose": "Source code"
    35→        }
    36→      },
    37→      "entry_point": "src/App.tsx",
    38→      "dependencies": [
    39→        "@tanstack/react-query",
    40→        "axios",
    41→        "clsx",
    42→        "fzstd",
    43→        "lucide-react",
    44→        "react",
    45→        "react-dom",
    46→        "react-router-dom",
    47→        "tailwind-merge"
    48→      ],
    49→      "dev_dependencies": [
    50→        "@eslint/js",
    51→        "@tailwindcss/vite",
    52→        "@types/node",
    53→        "@types/react",
    54→        "@types/react-dom",
    55→        "@vitejs/plugin-react",
    56→        "autoprefixer",
    57→        "eslint",
    58→        "eslint-plugin-react-hooks",
    59→        "eslint-plugin-react-refresh"
    60→      ],
    61→      "consumes": [
    62→        "server.api"
    63→      ]
    64→    },
    65→    "server": {
    66→      "name": "server",
    67→      "path": "C:\\Users\\MikeSwanson\\claude-projects\\guru-connect\\server",
    68→      "language": "Rust",
    69→      "framework": "Axum",
    70→      "type": "backend",
    71→      "package_manager": "cargo",
    72→      "default_port": 3000,
    73→      "key_directories": {
    74→        "src": {
    75→          "path": "src",
    76→          "purpose": "Source code"
    77→        }
    78→      },
    79→      "entry_point": "src/main.rs",
    80→      "api": {
    81→        "routes": [
    82→          {
    83→            "path": "/health",
    84→            "methods": [
    85→              "GET"
    86→            ],
    87→            "file": "src\\main.rs",
    88→            "framework": "Rust",
    89→            "requires_auth": false
    90→          },
    91→          {
    92→            "path": "/ws/agent",
    93→            "methods": [
    94→              "GET"
    95→            ],
    96→            "file": "src\\main.rs",
    97→            "framework": "Rust",
    98→            "requires_auth": false
    99→          },
   100→          {
   101→            "path": "/ws/viewer",
   102→            "methods": [
   103→              "GET"
   104→            ],
   105→            "file": "src\\main.rs",
   106→            "framework": "Rust",
   107→            "requires_auth": false
   108→          },
   109→          {
   110→            "path": "/api/sessions",
   111→            "methods": [
   112→              "GET"
   113→            ],
   114→            "file": "src\\main.rs",
   115→            "framework": "Rust",
   116→            "requires_auth": false
   117→          },
   118→          {
   119→            "path": "/api/sessions/:id",
   120→            "methods": [
   121→              "GET"
   122→            ],
   123→            "file": "src\\main.rs",
   124→            "framework": "Rust",
   125→            "requires_auth": false
   126→          }
   127→        ],
   128→        "total_routes": 5,
   129→        "methods": [
   130→          "GET"
   131→        ],
   132→        "protected_routes": []
   133→      },
   134→      "monitoring": {
   135→        "health_checks": [
   136→          "/health"
   137→        ]
   138→      }
   139→    }
   140→  },
   141→  "infrastructure": {},
   142→  "conventions": {}
   143→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

dashboard

@tanstack/react-query axios clsx fzstd lucide-react react react-dom react-router-dom tailwind-merge


     1→# GuruConnect
     2→
     3→Remote desktop solution similar to ScreenConnect, integrated with GuruRMM.
     4→
     5→## Project Overview
     6→
     7→GuruConnect provides remote screen control and backstage tools for Windows systems.
     8→It's designed to be fast, secure, and enterprise-ready.
     9→
    10→## Architecture
    11→
    12→```
    13→┌─────────────────┐         ┌─────────────────┐         ┌─────────────────┐
    14→│   Dashboard     │◄───────►│  GuruConnect    │◄───────►│  GuruConnect    │
    15→│   (React)       │  WSS    │  Server (Rust)  │  WSS    │  Agent (Rust)   │
    16→└─────────────────┘         └─────────────────┘         └─────────────────┘
    17→```
    18→
    19→## Directory Structure
    20→
    21→- `agent/` - Windows remote desktop agent (Rust)
    22→- `server/` - Relay server (Rust + Axum)
    23→- `dashboard/` - Web viewer (React, to be integrated with GuruRMM)
    24→- `proto/` - Protobuf protocol definitions
    25→
    26→## Building
    27→
    28→### Prerequisites
    29→
    30→- Rust 1.75+ (install via rustup)
    31→- Windows SDK (for agent)
    32→- protoc (Protocol Buffers compiler)
    33→
    34→### Build Commands
    35→
    36→```bash
    37→# Build all (from workspace root)
    38→cargo build --release
    39→
    40→# Build agent only
    41→cargo build -p guruconnect-agent --release
    42→
    43→# Build server only
    44→cargo build -p guruconnect-server --release
    45→```
    46→
    47→### Cross-compilation (Agent for Windows)
    48→
    49→From Linux build server:
    50→```bash
    51→# Install Windows target
    52→rustup target add x86_64-pc-windows-msvc
    53→
    54→# Build (requires cross or appropriate linker)
    55→cross build -p guruconnect-agent --target x86_64-pc-windows-msvc --release
    56→```
    57→
    58→## Development
    59→
    60→### Running the Server
    61→
    62→```bash
    63→# Development
    64→cargo run -p guruconnect-server
    65→
    66→# With environment variables
    67→DATABASE_URL=postgres://... JWT_SECRET=... cargo run -p guruconnect-server
    68→```
    69→
    70→### Testing the Agent
    71→
    72→The agent must be run on Windows:
    73→```powershell
    74→# Run from Windows
    75→.\target\release\guruconnect-agent.exe
    76→```
    77→
    78→## Protocol
    79→
    80→Uses Protocol Buffers for efficient message serialization.
    81→See `proto/guruconnect.proto` for message definitions.
    82→
    83→Key message types:
    84→- `VideoFrame` - Screen frames (raw+zstd, VP9, H264)
    85→- `MouseEvent` - Mouse input
    86→- `KeyEvent` - Keyboard input
    87→- `SessionRequest/Response` - Session management
    88→
    89→## Encoding Strategy
    90→
    91→| Scenario | Encoding |
    92→|----------|----------|
    93→| LAN (<20ms RTT) | Raw BGRA + Zstd + dirty rects |
    94→| WAN + GPU | H264 hardware |
    95→| WAN - GPU | VP9 software |
    96→
    97→## Key References
    98→
    99→- RustDesk source: `~/claude-projects/reference/rustdesk/`
   100→- GuruRMM: `~/claude-projects/gururmm/`
   101→- Plan: `~/.claude/plans/shimmering-wandering-crane.md`
   102→
   103→## Phase 1 MVP Goals
   104→
   105→1. DXGI screen capture with GDI fallback
   106→2. Raw + Zstd encoding with dirty rectangle detection
   107→3. Mouse and keyboard input injection
   108→4. WebSocket relay through server
   109→5. Basic React viewer
   110→
   111→## Security Considerations
   112→
   113→- All connections use TLS
   114→- JWT authentication for dashboard users
   115→- API key authentication for agents
   116→- Session audit logging
   117→- Optional session recording (Phase 4)
   118→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

# GuruConnect

Remote desktop solution similar to ScreenConnect, integrated with GuruRMM.

## Project Overview

GuruConnect provides remote screen control and backstage tools for Windows systems.
It's designed to be fast, secure, and enterprise-ready.

## Architecture

```
┌─────────────────┐         ┌─────────────────┐         ┌─────────────────┐
│   Dashboard     │◄───────►│  GuruConnect    │◄───────►│  GuruConnect    │
│   (React)       │  WSS    │  Server (Rust)  │  WSS    │  Agent (Rust)   │
└─────────────────┘         └─────────────────┘         └─────────────────┘
```

## Directory Structure

- `agent/` - Windows remote desktop agent (Rust)
- `server/` - Relay server (Rust + Axum)
- `dashboard/` - Web viewer (React, to be integrated with GuruRMM)
- `proto/` - Protobuf protocol definitions

## Building

### Prerequisites

- Rust 1.75+ (install via rustup)
- Windows SDK (for agent)
- protoc (Protocol Buffers compiler)

### Build Commands

```bash
# Build all (from workspace root)
cargo build --release

# Build agent only
cargo build -p guruconnect-agent --release

# Build server only
cargo build -p guruconnect-server --release
```

### Cross-compilation (Agent for Windows)

From Linux build server:
```bash
# Install Windows target
rustup target add x86_64-pc-windows-msvc

# Build (requires cross or appropriate linker)
cross build -p guruconnect-agent --target x86_64-pc-windows-msvc --release
```

## Development

### Running the Server

```bash
# Development
cargo run -p guruconnect-server

# With environment variables
DATABASE_URL=postgres://... JWT_SECRET=... cargo run -p guruconnect-server
```

### Testing the Agent

The agent must be run on Windows:
```powershell
# Run from Windows
.\target\release\guruconnect-agent.exe
```

## Protocol

Uses Protocol Buffers for efficient message serialization.
See `proto/guruconnect.proto` for message definitions.

Key message types:
- `VideoFrame` - Screen frames (raw+zstd, VP9, H264)
- `MouseEvent` - Mouse input
- `KeyEvent` - Keyboard input
- `SessionRequest/Response` - Session management

## Encoding Strategy

| Scenario | Encoding |
|----------|----------|
| LAN (<20ms RTT) | Raw BGRA + Zstd + dirty rects |
| WAN + GPU | H264 hardware |
| WAN - GPU | VP9 software |

## Key References

- RustDesk source: `~/claude-projects/reference/rustdesk/`
- GuruRMM: `~/claude-projects/gururmm/`
- Plan: `~/.claude/plans/shimmering-wandering-crane.md`

## Phase 1 MVP Goals

1. DXGI screen capture with GDI fallback
2. Raw + Zstd encoding with dirty rectangle detection
3. Mouse and keyboard input injection
4. WebSocket relay through server
5. Basic React viewer

## Security Considerations

- All connections use TLS
- JWT authentication for dashboard users
- API key authentication for agents
- Session audit logging
- Optional session recording (Phase 4)


Now let me explore the codebase structure to understand what documentation exists and what's missing. I'll look at:
1. Markdown files
2. Source code files and their comments
3. Proto files
4. Dashboard documentation


Let me explore the codebase structure to identify documentation coverage and gaps:


Now let me read the key source files to understand the documentation coverage and identify gaps. I'll look at:
1. Agent source files (Rust)
2. Server source files (Rust)
3. Dashboard source files (TypeScript/React)
4. Proto file


Let me examine the key source files to analyze their documentation coverage:


     1→syntax = "proto3";
     2→package guruconnect;
     3→
     4→// ============================================================================
     5→// Session Management
     6→// ============================================================================
     7→
     8→message SessionRequest {
     9→    string agent_id = 1;
    10→    string session_token = 2;
    11→    SessionType session_type = 3;
    12→    string client_version = 4;
    13→}
    14→
    15→message SessionResponse {
    16→    bool success = 1;
    17→    string session_id = 2;
    18→    string error = 3;
    19→    DisplayInfo display_info = 4;
    20→}
    21→
    22→enum SessionType {
    23→    SCREEN_CONTROL = 0;
    24→    VIEW_ONLY = 1;
    25→    BACKSTAGE = 2;
    26→    FILE_TRANSFER = 3;
    27→}
    28→
    29→// ============================================================================
    30→// Display Information
    31→// ============================================================================
    32→
    33→message DisplayInfo {
    34→    repeated Display displays = 1;
    35→    int32 primary_display = 2;
    36→}
    37→
    38→message Display {
    39→    int32 id = 1;
    40→    string name = 2;
    41→    int32 x = 3;
    42→    int32 y = 4;
    43→    int32 width = 5;
    44→    int32 height = 6;
    45→    bool is_primary = 7;
    46→}
    47→
    48→message SwitchDisplay {
    49→    int32 display_id = 1;
    50→}
    51→
    52→// ============================================================================
    53→// Video Frames
    54→// ============================================================================
    55→
    56→message VideoFrame {
    57→    int64 timestamp = 1;
    58→    int32 display_id = 2;
    59→    int32 sequence = 3;
    60→
    61→    oneof encoding {
    62→        RawFrame raw = 10;
    63→        EncodedFrame vp9 = 11;
    64→        EncodedFrame h264 = 12;
    65→        EncodedFrame h265 = 13;
    66→    }
    67→}
    68→
    69→message RawFrame {
    70→    int32 width = 1;
    71→    int32 height = 2;
    72→    bytes data = 3;             // Zstd compressed BGRA
    73→    bool compressed = 4;
    74→    repeated DirtyRect dirty_rects = 5;
    75→    bool is_keyframe = 6;       // Full frame vs incremental
    76→}
    77→
    78→message DirtyRect {
    79→    int32 x = 1;
    80→    int32 y = 2;
    81→    int32 width = 3;
    82→    int32 height = 4;
    83→}
    84→
    85→message EncodedFrame {
    86→    bytes data = 1;
    87→    bool keyframe = 2;
    88→    int64 pts = 3;
    89→    int64 dts = 4;
    90→}
    91→
    92→message VideoAck {
    93→    int32 sequence = 1;
    94→    int64 timestamp = 2;
    95→}
    96→
    97→// ============================================================================
    98→// Cursor
    99→// ============================================================================
   100→
   101→message CursorShape {
   102→    uint64 id = 1;
   103→    int32 hotspot_x = 2;
   104→    int32 hotspot_y = 3;
   105→    int32 width = 4;
   106→    int32 height = 5;
   107→    bytes data = 6;             // BGRA bitmap
   108→}
   109→
   110→message CursorPosition {
   111→    int32 x = 1;
   112→    int32 y = 2;
   113→    bool visible = 3;
   114→}
   115→
   116→// ============================================================================
   117→// Input Events
   118→// ============================================================================
   119→
   120→message MouseEvent {
   121→    int32 x = 1;
   122→    int32 y = 2;
   123→    MouseButtons buttons = 3;
   124→    int32 wheel_delta_x = 4;
   125→    int32 wheel_delta_y = 5;
   126→    MouseEventType event_type = 6;
   127→}
   128→
   129→enum MouseEventType {
   130→    MOUSE_MOVE = 0;
   131→    MOUSE_DOWN = 1;
   132→    MOUSE_UP = 2;
   133→    MOUSE_WHEEL = 3;
   134→}
   135→
   136→message MouseButtons {
   137→    bool left = 1;
   138→    bool right = 2;
   139→    bool middle = 3;
   140→    bool x1 = 4;
   141→    bool x2 = 5;
   142→}
   143→
   144→message KeyEvent {
   145→    bool down = 1;              // true = key down, false = key up
   146→    KeyEventType key_type = 2;
   147→    uint32 vk_code = 3;         // Virtual key code (Windows VK_*)
   148→    uint32 scan_code = 4;       // Hardware scan code
   149→    string unicode = 5;         // Unicode character (for text input)
   150→    Modifiers modifiers = 6;
   151→}
   152→
   153→enum KeyEventType {
   154→    KEY_VK = 0;                 // Virtual key code
   155→    KEY_SCAN = 1;               // Scan code
   156→    KEY_UNICODE = 2;            // Unicode character
   157→}
   158→
   159→message Modifiers {
   160→    bool ctrl = 1;
   161→    bool alt = 2;
   162→    bool shift = 3;
   163→    bool meta = 4;              // Windows key
   164→    bool caps_lock = 5;
   165→    bool num_lock = 6;
   166→}
   167→
   168→message SpecialKeyEvent {
   169→    SpecialKey key = 1;
   170→}
   171→
   172→enum SpecialKey {
   173→    CTRL_ALT_DEL = 0;
   174→    LOCK_SCREEN = 1;
   175→    PRINT_SCREEN = 2;
   176→}
   177→
   178→// ============================================================================
   179→// Clipboard
   180→// ============================================================================
   181→
   182→message ClipboardData {
   183→    ClipboardFormat format = 1;
   184→    bytes data = 2;
   185→    string mime_type = 3;
   186→}
   187→
   188→enum ClipboardFormat {
   189→    CLIPBOARD_TEXT = 0;
   190→    CLIPBOARD_HTML = 1;
   191→    CLIPBOARD_RTF = 2;
   192→    CLIPBOARD_IMAGE = 3;
   193→    CLIPBOARD_FILES = 4;
   194→}
   195→
   196→message ClipboardRequest {
   197→    // Request current clipboard content
   198→}
   199→
   200→// ============================================================================
   201→// Quality Control
   202→// ============================================================================
   203→
   204→message QualitySettings {
   205→    QualityPreset preset = 1;
   206→    int32 custom_fps = 2;       // 1-60
   207→    int32 custom_bitrate = 3;   // kbps
   208→    CodecPreference codec = 4;
   209→}
   210→
   211→enum QualityPreset {
   212→    QUALITY_AUTO = 0;
   213→    QUALITY_LOW = 1;            // Low bandwidth
   214→    QUALITY_BALANCED = 2;
   215→    QUALITY_HIGH = 3;           // Best quality
   216→}
   217→
   218→enum CodecPreference {
   219→    CODEC_AUTO = 0;
   220→    CODEC_RAW = 1;              // Raw + Zstd (LAN)
   221→    CODEC_VP9 = 2;
   222→    CODEC_H264 = 3;
   223→    CODEC_H265 = 4;
   224→}
   225→
   226→message LatencyReport {
   227→    int64 rtt_ms = 1;
   228→    int32 fps = 2;
   229→    int32 bitrate_kbps = 3;
   230→}
   231→
   232→// ============================================================================
   233→// Control Messages
   234→// ============================================================================
   235→
   236→message Heartbeat {
   237→    int64 timestamp = 1;
   238→}
   239→
   240→message HeartbeatAck {
   241→    int64 client_timestamp = 1;
   242→    int64 server_timestamp = 2;
   243→}
   244→
   245→message Disconnect {
   246→    string reason = 1;
   247→}
   248→
   249→// ============================================================================
   250→// Top-Level Message Wrapper
   251→// ============================================================================
   252→
   253→message Message {
   254→    oneof payload {
   255→        // Session
   256→        SessionRequest session_request = 1;
   257→        SessionResponse session_response = 2;
   258→
   259→        // Video
   260→        VideoFrame video_frame = 10;
   261→        VideoAck video_ack = 11;
   262→        SwitchDisplay switch_display = 12;
   263→
   264→        // Cursor
   265→        CursorShape cursor_shape = 15;
   266→        CursorPosition cursor_position = 16;
   267→
   268→        // Input
   269→        MouseEvent mouse_event = 20;
   270→        KeyEvent key_event = 21;
   271→        SpecialKeyEvent special_key = 22;
   272→
   273→        // Clipboard
   274→        ClipboardData clipboard_data = 30;
   275→        ClipboardRequest clipboard_request = 31;
   276→
   277→        // Quality
   278→        QualitySettings quality_settings = 40;
   279→        LatencyReport latency_report = 41;
   280→
   281→        // Control
   282→        Heartbeat heartbeat = 50;
   283→        HeartbeatAck heartbeat_ack = 51;
   284→        Disconnect disconnect = 52;
   285→    }
   286→}
   287→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

syntax = "proto3";
package guruconnect;

// ============================================================================
// Session Management
// ============================================================================

message SessionRequest {
    string agent_id = 1;
    string session_token = 2;
    SessionType session_type = 3;
    string client_version = 4;
}

message SessionResponse {
    bool success = 1;
    string session_id = 2;
    string error = 3;
    DisplayInfo display_info = 4;
}

enum SessionType {
    SCREEN_CONTROL = 0;
    VIEW_ONLY = 1;
    BACKSTAGE = 2;
    FILE_TRANSFER = 3;
}

// ============================================================================
// Display Information
// ============================================================================

message DisplayInfo {
    repeated Display displays = 1;
    int32 primary_display = 2;
}

message Display {
    int32 id = 1;
    string name = 2;
    int32 x = 3;
    int32 y = 4;
    int32 width = 5;
    int32 height = 6;
    bool is_primary = 7;
}

message SwitchDisplay {
    int32 display_id = 1;
}

// ============================================================================
// Video Frames
// ============================================================================

message VideoFrame {
    int64 timestamp = 1;
    int32 display_id = 2;
    int32 sequence = 3;

    oneof encoding {
        RawFrame raw = 10;
        EncodedFrame vp9 = 11;
        EncodedFrame h264 = 12;
        EncodedFrame h265 = 13;
    }
}

message RawFrame {
    int32 width = 1;
    int32 height = 2;
    bytes data = 3;             // Zstd compressed BGRA
    bool compressed = 4;
    repeated DirtyRect dirty_rects = 5;
    bool is_keyframe = 6;       // Full frame vs incremental
}

message DirtyRect {
    int32 x = 1;
    int32 y = 2;
    int32 width = 3;
    int32 height = 4;
}

message EncodedFrame {
    bytes data = 1;
    bool keyframe = 2;
    int64 pts = 3;
    int64 dts = 4;
}

message VideoAck {
    int32 sequence = 1;
    int64 timestamp = 2;
}

// ============================================================================
// Cursor
// ============================================================================

message CursorShape {
    uint64 id = 1;
    int32 hotspot_x = 2;
    int32 hotspot_y = 3;
    int32 width = 4;
    int32 height = 5;
    bytes data = 6;             // BGRA bitmap
}

message CursorPosition {
    int32 x = 1;
    int32 y = 2;
    bool visible = 3;
}

// ============================================================================
// Input Events
// ============================================================================

message MouseEvent {
    int32 x = 1;
    int32 y = 2;
    MouseButtons buttons = 3;
    int32 wheel_delta_x = 4;
    int32 wheel_delta_y = 5;
    MouseEventType event_type = 6;
}

enum MouseEventType {
    MOUSE_MOVE = 0;
    MOUSE_DOWN = 1;
    MOUSE_UP = 2;
    MOUSE_WHEEL = 3;
}

message MouseButtons {
    bool left = 1;
    bool right = 2;
    bool middle = 3;
    bool x1 = 4;
    bool x2 = 5;
}

message KeyEvent {
    bool down = 1;              // true = key down, false = key up
    KeyEventType key_type = 2;
    uint32 vk_code = 3;         // Virtual key code (Windows VK_*)
    uint32 scan_code = 4;       // Hardware scan code
    string unicode = 5;         // Unicode character (for text input)
    Modifiers modifiers = 6;
}

enum KeyEventType {
    KEY_VK = 0;                 // Virtual key code
    KEY_SCAN = 1;               // Scan code
    KEY_UNICODE = 2;            // Unicode character
}

message Modifiers {
    bool ctrl = 1;
    bool alt = 2;
    bool shift = 3;
    bool meta = 4;              // Windows key
    bool caps_lock = 5;
    bool num_lock = 6;
}

message SpecialKeyEvent {
    SpecialKey key = 1;
}

enum SpecialKey {
    CTRL_ALT_DEL = 0;
    LOCK_SCREEN = 1;
    PRINT_SCREEN = 2;
}

// ============================================================================
// Clipboard
// ============================================================================

message ClipboardData {
    ClipboardFormat format = 1;
    bytes data = 2;
    string mime_type = 3;
}

enum ClipboardFormat {
    CLIPBOARD_TEXT = 0;
    CLIPBOARD_HTML = 1;
    CLIPBOARD_RTF = 2;
    CLIPBOARD_IMAGE = 3;
    CLIPBOARD_FILES = 4;
}

message ClipboardRequest {
    // Request current clipboard content
}

// ============================================================================
// Quality Control
// ============================================================================

message QualitySettings {
    QualityPreset preset = 1;
    int32 custom_fps = 2;       // 1-60
    int32 custom_bitrate = 3;   // kbps
    CodecPreference codec = 4;
}

enum QualityPreset {
    QUALITY_AUTO = 0;
    QUALITY_LOW = 1;            // Low bandwidth
    QUALITY_BALANCED = 2;
    QUALITY_HIGH = 3;           // Best quality
}

enum CodecPreference {
    CODEC_AUTO = 0;
    CODEC_RAW = 1;              // Raw + Zstd (LAN)
    CODEC_VP9 = 2;
    CODEC_H264 = 3;
    CODEC_H265 = 4;
}

message LatencyReport {
    int64 rtt_ms = 1;
    int32 fps = 2;
    int32 bitrate_kbps = 3;
}

// ============================================================================
// Control Messages
// ============================================================================

message Heartbeat {
    int64 timestamp = 1;
}

message HeartbeatAck {
    int64 client_timestamp = 1;
    int64 server_timestamp = 2;
}

message Disconnect {
    string reason = 1;
}

// ============================================================================
// Top-Level Message Wrapper
// ============================================================================

message Message {
    oneof payload {
        // Session
        SessionRequest session_request = 1;
        SessionResponse session_response = 2;

        // Video
        VideoFrame video_frame = 10;
        VideoAck video_ack = 11;
        SwitchDisplay switch_display = 12;

        // Cursor
        CursorShape cursor_shape = 15;
        CursorPosition cursor_position = 16;

        // Input
        MouseEvent mouse_event = 20;
        KeyEvent key_event = 21;
        SpecialKeyEvent special_key = 22;

        // Clipboard
        ClipboardData clipboard_data = 30;
        ClipboardRequest clipboard_request = 31;

        // Quality
        QualitySettings quality_settings = 40;
        LatencyReport latency_report = 41;

        // Control
        Heartbeat heartbeat = 50;
        HeartbeatAck heartbeat_ack = 51;
        Disconnect disconnect = 52;
    }
}


     1→//! GuruConnect Agent - Remote Desktop Agent for Windows
     2→//!
     3→//! Provides screen capture, input injection, and remote control capabilities.
     4→
     5→mod capture;
     6→mod config;
     7→mod encoder;
     8→mod input;
     9→mod session;
    10→mod transport;
    11→
    12→pub mod proto {
    13→    include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
    14→}
    15→
    16→use anyhow::Result;
    17→use tracing::{info, error, Level};
    18→use tracing_subscriber::FmtSubscriber;
    19→
    20→#[tokio::main]
    21→async fn main() -> Result<()> {
    22→    // Initialize logging
    23→    let subscriber = FmtSubscriber::builder()
    24→        .with_max_level(Level::INFO)
    25→        .with_target(true)
    26→        .with_thread_ids(true)
    27→        .init();
    28→
    29→    info!("GuruConnect Agent v{}", env!("CARGO_PKG_VERSION"));
    30→
    31→    // Load configuration
    32→    let config = config::Config::load()?;
    33→    info!("Loaded configuration for server: {}", config.server_url);
    34→
    35→    // Run the agent
    36→    if let Err(e) = run_agent(config).await {
    37→        error!("Agent error: {}", e);
    38→        return Err(e);
    39→    }
    40→
    41→    Ok(())
    42→}
    43→
    44→async fn run_agent(config: config::Config) -> Result<()> {
    45→    // Create session manager
    46→    let mut session = session::SessionManager::new(config.clone());
    47→
    48→    // Connect to server and run main loop
    49→    loop {
    50→        info!("Connecting to server...");
    51→
    52→        match session.connect().await {
    53→            Ok(_) => {
    54→                info!("Connected to server");
    55→
    56→                // Run session until disconnect
    57→                if let Err(e) = session.run().await {
    58→                    error!("Session error: {}", e);
    59→                }
    60→            }
    61→            Err(e) => {
    62→                error!("Connection failed: {}", e);
    63→            }
    64→        }
    65→
    66→        // Wait before reconnecting
    67→        info!("Reconnecting in 5 seconds...");
    68→        tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
    69→    }
    70→}
    71→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! GuruConnect Agent - Remote Desktop Agent for Windows
//!
//! Provides screen capture, input injection, and remote control capabilities.

mod capture;
mod config;
mod encoder;
mod input;
mod session;
mod transport;

pub mod proto {
    include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
}

use anyhow::Result;
use tracing::{info, error, Level};
use tracing_subscriber::FmtSubscriber;

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize logging
    let subscriber = FmtSubscriber::builder()
        .with_max_level(Level::INFO)
        .with_target(true)
        .with_thread_ids(true)
        .init();

    info!("GuruConnect Agent v{}", env!("CARGO_PKG_VERSION"));

    // Load configuration
    let config = config::Config::load()?;
    info!("Loaded configuration for server: {}", config.server_url);

    // Run the agent
    if let Err(e) = run_agent(config).await {
        error!("Agent error: {}", e);
        return Err(e);
    }

    Ok(())
}

async fn run_agent(config: config::Config) -> Result<()> {
    // Create session manager
    let mut session = session::SessionManager::new(config.clone());

    // Connect to server and run main loop
    loop {
        info!("Connecting to server...");

        match session.connect().await {
            Ok(_) => {
                info!("Connected to server");

                // Run session until disconnect
                if let Err(e) = session.run().await {
                    error!("Session error: {}", e);
                }
            }
            Err(e) => {
                error!("Connection failed: {}", e);
            }
        }

        // Wait before reconnecting
        info!("Reconnecting in 5 seconds...");
        tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
    }
}


     1→//! GuruConnect Server - WebSocket Relay Server
     2→//!
     3→//! Handles connections from both agents and dashboard viewers,
     4→//! relaying video frames and input events between them.
     5→
     6→mod config;
     7→mod relay;
     8→mod session;
     9→mod auth;
    10→mod api;
    11→mod db;
    12→
    13→pub mod proto {
    14→    include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
    15→}
    16→
    17→use anyhow::Result;
    18→use axum::{
    19→    Router,
    20→    routing::get,
    21→};
    22→use std::net::SocketAddr;
    23→use tower_http::cors::{Any, CorsLayer};
    24→use tower_http::trace::TraceLayer;
    25→use tracing::{info, Level};
    26→use tracing_subscriber::FmtSubscriber;
    27→
    28→#[tokio::main]
    29→async fn main() -> Result<()> {
    30→    // Initialize logging
    31→    let _subscriber = FmtSubscriber::builder()
    32→        .with_max_level(Level::INFO)
    33→        .with_target(true)
    34→        .init();
    35→
    36→    info!("GuruConnect Server v{}", env!("CARGO_PKG_VERSION"));
    37→
    38→    // Load configuration
    39→    let config = config::Config::load()?;
    40→    info!("Loaded configuration, listening on {}", config.listen_addr);
    41→
    42→    // Initialize database connection (optional for MVP)
    43→    // let db = db::init(&config.database_url).await?;
    44→
    45→    // Create session manager
    46→    let sessions = session::SessionManager::new();
    47→
    48→    // Build router
    49→    let app = Router::new()
    50→        // Health check
    51→        .route("/health", get(health))
    52→        // WebSocket endpoints
    53→        .route("/ws/agent", get(relay::agent_ws_handler))
    54→        .route("/ws/viewer", get(relay::viewer_ws_handler))
    55→        // REST API
    56→        .route("/api/sessions", get(api::list_sessions))
    57→        .route("/api/sessions/:id", get(api::get_session))
    58→        // State
    59→        .with_state(sessions)
    60→        // Middleware
    61→        .layer(TraceLayer::new_for_http())
    62→        .layer(
    63→            CorsLayer::new()
    64→                .allow_origin(Any)
    65→                .allow_methods(Any)
    66→                .allow_headers(Any),
    67→        );
    68→
    69→    // Start server
    70→    let addr: SocketAddr = config.listen_addr.parse()?;
    71→    let listener = tokio::net::TcpListener::bind(addr).await?;
    72→
    73→    info!("Server listening on {}", addr);
    74→
    75→    axum::serve(listener, app).await?;
    76→
    77→    Ok(())
    78→}
    79→
    80→async fn health() -> &'static str {
    81→    "OK"
    82→}
    83→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! GuruConnect Server - WebSocket Relay Server
//!
//! Handles connections from both agents and dashboard viewers,
//! relaying video frames and input events between them.

mod config;
mod relay;
mod session;
mod auth;
mod api;
mod db;

pub mod proto {
    include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
}

use anyhow::Result;
use axum::{
    Router,
    routing::get,
};
use std::net::SocketAddr;
use tower_http::cors::{Any, CorsLayer};
use tower_http::trace::TraceLayer;
use tracing::{info, Level};
use tracing_subscriber::FmtSubscriber;

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize logging
    let _subscriber = FmtSubscriber::builder()
        .with_max_level(Level::INFO)
        .with_target(true)
        .init();

    info!("GuruConnect Server v{}", env!("CARGO_PKG_VERSION"));

    // Load configuration
    let config = config::Config::load()?;
    info!("Loaded configuration, listening on {}", config.listen_addr);

    // Initialize database connection (optional for MVP)
    // let db = db::init(&config.database_url).await?;

    // Create session manager
    let sessions = session::SessionManager::new();

    // Build router
    let app = Router::new()
        // Health check
        .route("/health", get(health))
        // WebSocket endpoints
        .route("/ws/agent", get(relay::agent_ws_handler))
        .route("/ws/viewer", get(relay::viewer_ws_handler))
        // REST API
        .route("/api/sessions", get(api::list_sessions))
        .route("/api/sessions/:id", get(api::get_session))
        // State
        .with_state(sessions)
        // Middleware
        .layer(TraceLayer::new_for_http())
        .layer(
            CorsLayer::new()
                .allow_origin(Any)
                .allow_methods(Any)
                .allow_headers(Any),
        );

    // Start server
    let addr: SocketAddr = config.listen_addr.parse()?;
    let listener = tokio::net::TcpListener::bind(addr).await?;

    info!("Server listening on {}", addr);

    axum::serve(listener, app).await?;

    Ok(())
}

async fn health() -> &'static str {
    "OK"
}


     1→//! Screen capture module
     2→//!
     3→//! Provides DXGI Desktop Duplication for high-performance screen capture on Windows 8+,
     4→//! with GDI fallback for legacy systems or edge cases.
     5→
     6→#[cfg(windows)]
     7→mod dxgi;
     8→#[cfg(windows)]
     9→mod gdi;
    10→mod display;
    11→
    12→pub use display::{Display, DisplayInfo};
    13→
    14→use anyhow::Result;
    15→use std::time::Instant;
    16→
    17→/// Captured frame data
    18→#[derive(Debug)]
    19→pub struct CapturedFrame {
    20→    /// Frame width in pixels
    21→    pub width: u32,
    22→
    23→    /// Frame height in pixels
    24→    pub height: u32,
    25→
    26→    /// Raw BGRA pixel data (4 bytes per pixel)
    27→    pub data: Vec<u8>,
    28→
    29→    /// Timestamp when frame was captured
    30→    pub timestamp: Instant,
    31→
    32→    /// Display ID this frame is from
    33→    pub display_id: u32,
    34→
    35→    /// Regions that changed since last frame (if available)
    36→    pub dirty_rects: Option<Vec<DirtyRect>>,
    37→}
    38→
    39→/// Rectangular region that changed
    40→#[derive(Debug, Clone, Copy)]
    41→pub struct DirtyRect {
    42→    pub x: u32,
    43→    pub y: u32,
    44→    pub width: u32,
    45→    pub height: u32,
    46→}
    47→
    48→/// Screen capturer trait
    49→pub trait Capturer: Send {
    50→    /// Capture the next frame
    51→    ///
    52→    /// Returns None if no new frame is available (screen unchanged)
    53→    fn capture(&mut self) -> Result<Option<CapturedFrame>>;
    54→
    55→    /// Get the current display info
    56→    fn display(&self) -> &Display;
    57→
    58→    /// Check if capturer is still valid (display may have changed)
    59→    fn is_valid(&self) -> bool;
    60→}
    61→
    62→/// Create a capturer for the specified display
    63→#[cfg(windows)]
    64→pub fn create_capturer(display: Display, use_dxgi: bool, gdi_fallback: bool) -> Result<Box<dyn Capturer>> {
    65→    if use_dxgi {
    66→        match dxgi::DxgiCapturer::new(display.clone()) {
    67→            Ok(capturer) => {
    68→                tracing::info!("Using DXGI Desktop Duplication for capture");
    69→                return Ok(Box::new(capturer));
    70→            }
    71→            Err(e) => {
    72→                tracing::warn!("DXGI capture failed: {}, trying fallback", e);
    73→                if !gdi_fallback {
    74→                    return Err(e);
    75→                }
    76→            }
    77→        }
    78→    }
    79→
    80→    // GDI fallback
    81→    tracing::info!("Using GDI for capture");
    82→    Ok(Box::new(gdi::GdiCapturer::new(display)?))
    83→}
    84→
    85→#[cfg(not(windows))]
    86→pub fn create_capturer(_display: Display, _use_dxgi: bool, _gdi_fallback: bool) -> Result<Box<dyn Capturer>> {
    87→    anyhow::bail!("Screen capture only supported on Windows")
    88→}
    89→
    90→/// Get all available displays
    91→pub fn enumerate_displays() -> Result<Vec<Display>> {
    92→    display::enumerate_displays()
    93→}
    94→
    95→/// Get the primary display
    96→pub fn primary_display() -> Result<Display> {
    97→    let displays = enumerate_displays()?;
    98→    displays
    99→        .into_iter()
   100→        .find(|d| d.is_primary)
   101→        .ok_or_else(|| anyhow::anyhow!("No primary display found"))
   102→}
   103→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! Screen capture module
//!
//! Provides DXGI Desktop Duplication for high-performance screen capture on Windows 8+,
//! with GDI fallback for legacy systems or edge cases.

#[cfg(windows)]
mod dxgi;
#[cfg(windows)]
mod gdi;
mod display;

pub use display::{Display, DisplayInfo};

use anyhow::Result;
use std::time::Instant;

/// Captured frame data
#[derive(Debug)]
pub struct CapturedFrame {
    /// Frame width in pixels
    pub width: u32,

    /// Frame height in pixels
    pub height: u32,

    /// Raw BGRA pixel data (4 bytes per pixel)
    pub data: Vec<u8>,

    /// Timestamp when frame was captured
    pub timestamp: Instant,

    /// Display ID this frame is from
    pub display_id: u32,

    /// Regions that changed since last frame (if available)
    pub dirty_rects: Option<Vec<DirtyRect>>,
}

/// Rectangular region that changed
#[derive(Debug, Clone, Copy)]
pub struct DirtyRect {
    pub x: u32,
    pub y: u32,
    pub width: u32,
    pub height: u32,
}

/// Screen capturer trait
pub trait Capturer: Send {
    /// Capture the next frame
    ///
    /// Returns None if no new frame is available (screen unchanged)
    fn capture(&mut self) -> Result<Option<CapturedFrame>>;

    /// Get the current display info
    fn display(&self) -> &Display;

    /// Check if capturer is still valid (display may have changed)
    fn is_valid(&self) -> bool;
}

/// Create a capturer for the specified display
#[cfg(windows)]
pub fn create_capturer(display: Display, use_dxgi: bool, gdi_fallback: bool) -> Result<Box<dyn Capturer>> {
    if use_dxgi {
        match dxgi::DxgiCapturer::new(display.clone()) {
            Ok(capturer) => {
                tracing::info!("Using DXGI Desktop Duplication for capture");
                return Ok(Box::new(capturer));
            }
            Err(e) => {
                tracing::warn!("DXGI capture failed: {}, trying fallback", e);
                if !gdi_fallback {
                    return Err(e);
                }
            }
        }
    }

    // GDI fallback
    tracing::info!("Using GDI for capture");
    Ok(Box::new(gdi::GdiCapturer::new(display)?))
}

#[cfg(not(windows))]
pub fn create_capturer(_display: Display, _use_dxgi: bool, _gdi_fallback: bool) -> Result<Box<dyn Capturer>> {
    anyhow::bail!("Screen capture only supported on Windows")
}

/// Get all available displays
pub fn enumerate_displays() -> Result<Vec<Display>> {
    display::enumerate_displays()
}

/// Get the primary display
pub fn primary_display() -> Result<Display> {
    let displays = enumerate_displays()?;
    displays
        .into_iter()
        .find(|d| d.is_primary)
        .ok_or_else(|| anyhow::anyhow!("No primary display found"))
}


     1→//! WebSocket relay handlers
     2→//!
     3→//! Handles WebSocket connections from agents and viewers,
     4→//! relaying video frames and input events between them.
     5→
     6→use axum::{
     7→    extract::{
     8→        ws::{Message, WebSocket, WebSocketUpgrade},
     9→        Query, State,
    10→    },
    11→    response::IntoResponse,
    12→};
    13→use futures_util::{SinkExt, StreamExt};
    14→use prost::Message as ProstMessage;
    15→use serde::Deserialize;
    16→use tracing::{error, info, warn};
    17→
    18→use crate::proto;
    19→use crate::session::SessionManager;
    20→
    21→#[derive(Debug, Deserialize)]
    22→pub struct AgentParams {
    23→    agent_id: String,
    24→    #[serde(default)]
    25→    agent_name: Option<String>,
    26→}
    27→
    28→#[derive(Debug, Deserialize)]
    29→pub struct ViewerParams {
    30→    session_id: String,
    31→}
    32→
    33→/// WebSocket handler for agent connections
    34→pub async fn agent_ws_handler(
    35→    ws: WebSocketUpgrade,
    36→    State(sessions): State<SessionManager>,
    37→    Query(params): Query<AgentParams>,
    38→) -> impl IntoResponse {
    39→    let agent_id = params.agent_id;
    40→    let agent_name = params.agent_name.unwrap_or_else(|| agent_id.clone());
    41→
    42→    ws.on_upgrade(move |socket| handle_agent_connection(socket, sessions, agent_id, agent_name))
    43→}
    44→
    45→/// WebSocket handler for viewer connections
    46→pub async fn viewer_ws_handler(
    47→    ws: WebSocketUpgrade,
    48→    State(sessions): State<SessionManager>,
    49→    Query(params): Query<ViewerParams>,
    50→) -> impl IntoResponse {
    51→    let session_id = params.session_id;
    52→
    53→    ws.on_upgrade(move |socket| handle_viewer_connection(socket, sessions, session_id))
    54→}
    55→
    56→/// Handle an agent WebSocket connection
    57→async fn handle_agent_connection(
    58→    socket: WebSocket,
    59→    sessions: SessionManager,
    60→    agent_id: String,
    61→    agent_name: String,
    62→) {
    63→    info!("Agent connected: {} ({})", agent_name, agent_id);
    64→
    65→    // Register the agent and get channels
    66→    let (session_id, frame_tx, mut input_rx) = sessions.register_agent(agent_id.clone(), agent_name.clone()).await;
    67→
    68→    info!("Session created: {}", session_id);
    69→
    70→    let (mut ws_sender, mut ws_receiver) = socket.split();
    71→
    72→    // Task to forward input events from viewers to agent
    73→    let input_forward = tokio::spawn(async move {
    74→        while let Some(input_data) = input_rx.recv().await {
    75→            if ws_sender.send(Message::Binary(input_data.into())).await.is_err() {
    76→                break;
    77→            }
    78→        }
    79→    });
    80→
    81→    // Main loop: receive frames from agent and broadcast to viewers
    82→    while let Some(msg) = ws_receiver.next().await {
    83→        match msg {
    84→            Ok(Message::Binary(data)) => {
    85→                // Try to decode as protobuf message
    86→                match proto::Message::decode(data.as_ref()) {
    87→                    Ok(proto_msg) => {
    88→                        if let Some(proto::message::Payload::VideoFrame(_)) = &proto_msg.payload {
    89→                            // Broadcast frame to all viewers
    90→                            let _ = frame_tx.send(data.to_vec());
    91→                        }
    92→                    }
    93→                    Err(e) => {
    94→                        warn!("Failed to decode agent message: {}", e);
    95→                    }
    96→                }
    97→            }
    98→            Ok(Message::Close(_)) => {
    99→                info!("Agent disconnected: {}", agent_id);
   100→                break;
   101→            }
   102→            Ok(Message::Ping(data)) => {
   103→                // Pong is handled automatically by axum
   104→                let _ = data;
   105→            }
   106→            Ok(_) => {}
   107→            Err(e) => {
   108→                error!("WebSocket error from agent {}: {}", agent_id, e);
   109→                break;
   110→            }
   111→        }
   112→    }
   113→
   114→    // Cleanup
   115→    input_forward.abort();
   116→    sessions.remove_session(session_id).await;
   117→    info!("Session {} ended", session_id);
   118→}
   119→
   120→/// Handle a viewer WebSocket connection
   121→async fn handle_viewer_connection(
   122→    socket: WebSocket,
   123→    sessions: SessionManager,
   124→    session_id_str: String,
   125→) {
   126→    // Parse session ID
   127→    let session_id = match uuid::Uuid::parse_str(&session_id_str) {
   128→        Ok(id) => id,
   129→        Err(_) => {
   130→            warn!("Invalid session ID: {}", session_id_str);
   131→            return;
   132→        }
   133→    };
   134→
   135→    // Join the session
   136→    let (mut frame_rx, input_tx) = match sessions.join_session(session_id).await {
   137→        Some(channels) => channels,
   138→        None => {
   139→            warn!("Session not found: {}", session_id);
   140→            return;
   141→        }
   142→    };
   143→
   144→    info!("Viewer joined session: {}", session_id);
   145→
   146→    let (mut ws_sender, mut ws_receiver) = socket.split();
   147→
   148→    // Task to forward frames from agent to this viewer
   149→    let frame_forward = tokio::spawn(async move {
   150→        while let Ok(frame_data) = frame_rx.recv().await {
   151→            if ws_sender.send(Message::Binary(frame_data.into())).await.is_err() {
   152→                break;
   153→            }
   154→        }
   155→    });
   156→
   157→    // Main loop: receive input from viewer and forward to agent
   158→    while let Some(msg) = ws_receiver.next().await {
   159→        match msg {
   160→            Ok(Message::Binary(data)) => {
   161→                // Try to decode as protobuf message
   162→                match proto::Message::decode(data.as_ref()) {
   163→                    Ok(proto_msg) => {
   164→                        match &proto_msg.payload {
   165→                            Some(proto::message::Payload::MouseEvent(_)) |
   166→                            Some(proto::message::Payload::KeyEvent(_)) => {
   167→                                // Forward input to agent
   168→                                let _ = input_tx.send(data.to_vec()).await;
   169→                            }
   170→                            _ => {}
   171→                        }
   172→                    }
   173→                    Err(e) => {
   174→                        warn!("Failed to decode viewer message: {}", e);
   175→                    }
   176→                }
   177→            }
   178→            Ok(Message::Close(_)) => {
   179→                info!("Viewer disconnected from session: {}", session_id);
   180→                break;
   181→            }
   182→            Ok(_) => {}
   183→            Err(e) => {
   184→                error!("WebSocket error from viewer: {}", e);
   185→                break;
   186→            }
   187→        }
   188→    }
   189→
   190→    // Cleanup
   191→    frame_forward.abort();
   192→    sessions.leave_session(session_id).await;
   193→    info!("Viewer left session: {}", session_id);
   194→}
   195→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! WebSocket relay handlers
//!
//! Handles WebSocket connections from agents and viewers,
//! relaying video frames and input events between them.

use axum::{
    extract::{
        ws::{Message, WebSocket, WebSocketUpgrade},
        Query, State,
    },
    response::IntoResponse,
};
use futures_util::{SinkExt, StreamExt};
use prost::Message as ProstMessage;
use serde::Deserialize;
use tracing::{error, info, warn};

use crate::proto;
use crate::session::SessionManager;

#[derive(Debug, Deserialize)]
pub struct AgentParams {
    agent_id: String,
    #[serde(default)]
    agent_name: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct ViewerParams {
    session_id: String,
}

/// WebSocket handler for agent connections
pub async fn agent_ws_handler(
    ws: WebSocketUpgrade,
    State(sessions): State<SessionManager>,
    Query(params): Query<AgentParams>,
) -> impl IntoResponse {
    let agent_id = params.agent_id;
    let agent_name = params.agent_name.unwrap_or_else(|| agent_id.clone());

    ws.on_upgrade(move |socket| handle_agent_connection(socket, sessions, agent_id, agent_name))
}

/// WebSocket handler for viewer connections
pub async fn viewer_ws_handler(
    ws: WebSocketUpgrade,
    State(sessions): State<SessionManager>,
    Query(params): Query<ViewerParams>,
) -> impl IntoResponse {
    let session_id = params.session_id;

    ws.on_upgrade(move |socket| handle_viewer_connection(socket, sessions, session_id))
}

/// Handle an agent WebSocket connection
async fn handle_agent_connection(
    socket: WebSocket,
    sessions: SessionManager,
    agent_id: String,
    agent_name: String,
) {
    info!("Agent connected: {} ({})", agent_name, agent_id);

    // Register the agent and get channels
    let (session_id, frame_tx, mut input_rx) = sessions.register_agent(agent_id.clone(), agent_name.clone()).await;

    info!("Session created: {}", session_id);

    let (mut ws_sender, mut ws_receiver) = socket.split();

    // Task to forward input events from viewers to agent
    let input_forward = tokio::spawn(async move {
        while let Some(input_data) = input_rx.recv().await {
            if ws_sender.send(Message::Binary(input_data.into())).await.is_err() {
                break;
            }
        }
    });

    // Main loop: receive frames from agent and broadcast to viewers
    while let Some(msg) = ws_receiver.next().await {
        match msg {
            Ok(Message::Binary(data)) => {
                // Try to decode as protobuf message
                match proto::Message::decode(data.as_ref()) {
                    Ok(proto_msg) => {
                        if let Some(proto::message::Payload::VideoFrame(_)) = &proto_msg.payload {
                            // Broadcast frame to all viewers
                            let _ = frame_tx.send(data.to_vec());
                        }
                    }
                    Err(e) => {
                        warn!("Failed to decode agent message: {}", e);
                    }
                }
            }
            Ok(Message::Close(_)) => {
                info!("Agent disconnected: {}", agent_id);
                break;
            }
            Ok(Message::Ping(data)) => {
                // Pong is handled automatically by axum
                let _ = data;
            }
            Ok(_) => {}
            Err(e) => {
                error!("WebSocket error from agent {}: {}", agent_id, e);
                break;
            }
        }
    }

    // Cleanup
    input_forward.abort();
    sessions.remove_session(session_id).await;
    info!("Session {} ended", session_id);
}

/// Handle a viewer WebSocket connection
async fn handle_viewer_connection(
    socket: WebSocket,
    sessions: SessionManager,
    session_id_str: String,
) {
    // Parse session ID
    let session_id = match uuid::Uuid::parse_str(&session_id_str) {
        Ok(id) => id,
        Err(_) => {
            warn!("Invalid session ID: {}", session_id_str);
            return;
        }
    };

    // Join the session
    let (mut frame_rx, input_tx) = match sessions.join_session(session_id).await {
        Some(channels) => channels,
        None => {
            warn!("Session not found: {}", session_id);
            return;
        }
    };

    info!("Viewer joined session: {}", session_id);

    let (mut ws_sender, mut ws_receiver) = socket.split();

    // Task to forward frames from agent to this viewer
    let frame_forward = tokio::spawn(async move {
        while let Ok(frame_data) = frame_rx.recv().await {
            if ws_sender.send(Message::Binary(frame_data.into())).await.is_err() {
                break;
            }
        }
    });

    // Main loop: receive input from viewer and forward to agent
    while let Some(msg) = ws_receiver.next().await {
        match msg {
            Ok(Message::Binary(data)) => {
                // Try to decode as protobuf message
                match proto::Message::decode(data.as_ref()) {
                    Ok(proto_msg) => {
                        match &proto_msg.payload {
                            Some(proto::message::Payload::MouseEvent(_)) |
                            Some(proto::message::Payload::KeyEvent(_)) => {
                                // Forward input to agent
                                let _ = input_tx.send(data.to_vec()).await;
                            }
                            _ => {}
                        }
                    }
                    Err(e) => {
                        warn!("Failed to decode viewer message: {}", e);
                    }
                }
            }
            Ok(Message::Close(_)) => {
                info!("Viewer disconnected from session: {}", session_id);
                break;
            }
            Ok(_) => {}
            Err(e) => {
                error!("WebSocket error from viewer: {}", e);
                break;
            }
        }
    }

    // Cleanup
    frame_forward.abort();
    sessions.leave_session(session_id).await;
    info!("Viewer left session: {}", session_id);
}


Now let me read more source files to understand the full picture - specifically the dashboard components and other agent/server modules:


Let me explore more of the codebase to understand the full documentation coverage:


1→/**
     2→ * RemoteViewer Component
     3→ *
     4→ * Canvas-based remote desktop viewer that connects to a GuruConnect
     5→ * agent via the relay server. Handles frame rendering and input capture.
     6→ */
     7→
     8→import React, { useRef, useEffect, useCallback, useState } from 'react';
     9→import { useRemoteSession, createMouseEvent, createKeyEvent } from '../hooks/useRemoteSession';
    10→import type { VideoFrame, ConnectionStatus } from '../types/protocol';
    11→
    12→interface RemoteViewerProps {
    13→  serverUrl: string;
    14→  sessionId: string;
    15→  className?: string;
    16→  onStatusChange?: (status: ConnectionStatus) => void;
    17→  autoConnect?: boolean;
    18→  showStatusBar?: boolean;
    19→}
    20→
    21→export const RemoteViewer: React.FC<RemoteViewerProps> = ({
    22→  serverUrl,
    23→  sessionId,
    24→  className = '',
    25→  onStatusChange,
    26→  autoConnect = true,
    27→  showStatusBar = true,
    28→}) => {
    29→  const canvasRef = useRef<HTMLCanvasElement>(null);
    30→  const containerRef = useRef<HTMLDivElement>(null);
    31→  const ctxRef = useRef<CanvasRenderingContext2D | null>(null);
    32→
    33→  // Display dimensions from received frames
    34→  const [displaySize, setDisplaySize] = useState({ width: 1920, height: 1080 });
    35→
    36→  // Frame buffer for rendering
    37→  const frameBufferRef = useRef<ImageData | null>(null);
    38→
    39→  // Handle incoming video frames
    40→  const handleFrame = useCallback((frame: VideoFrame) => {
    41→    if (!frame.raw || !canvasRef.current) return;
    42→
    43→    const { width, height, data } = frame.raw;
    44→
    45→    // Update display size if changed
    46→    if (width !== displaySize.width || height !== displaySize.height) {
    47→      setDisplaySize({ width, height });
    48→    }
    49→
    50→    // Get or create context
    51→    if (!ctxRef.current) {
    52→      ctxRef.current = canvasRef.current.getContext('2d', {
    53→        alpha: false,
    54→        desynchronized: true,
    55→      });
    56→    }
    57→
    58→    const ctx = ctxRef.current;
    59→    if (!ctx) return;
    60→
    61→    // For MVP, we assume raw BGRA frames
    62→    // In production, handle compressed frames with fzstd
    63→    let frameData = data;
    64→
    65→    // Create or reuse ImageData
    66→    if (!frameBufferRef.current ||
    67→        frameBufferRef.current.width !== width ||
    68→        frameBufferRef.current.height !== height) {
    69→      frameBufferRef.current = ctx.createImageData(width, height);
    70→    }
    71→
    72→    const imageData = frameBufferRef.current;
    73→
    74→    // Convert BGRA to RGBA for canvas
    75→    const pixels = imageData.data;
    76→    const len = Math.min(frameData.length, pixels.length);
    77→
    78→    for (let i = 0; i < len; i += 4) {
    79→      pixels[i] = frameData[i + 2];     // R <- B
    80→      pixels[i + 1] = frameData[i + 1]; // G <- G
    81→      pixels[i + 2] = frameData[i];     // B <- R
    82→      pixels[i + 3] = 255;              // A (opaque)
    83→    }
    84→
    85→    // Draw to canvas
    86→    ctx.putImageData(imageData, 0, 0);
    87→  }, [displaySize]);
    88→
    89→  // Set up session
    90→  const { status, connect, disconnect, sendMouseEvent, sendKeyEvent } = useRemoteSession({
    91→    serverUrl,
    92→    sessionId,
    93→    onFrame: handleFrame,
    94→    onStatusChange,
    95→  });
    96→
    97→  // Auto-connect on mount
    98→  useEffect(() => {
    99→    if (autoConnect) {
   100→      connect();
   101→    }
   102→    return () => {
   103→      disconnect();
   104→    };
   105→  }, [autoConnect, connect, disconnect]);
   106→
   107→  // Update canvas size when display size changes
   108→  useEffect(() => {
   109→    if (canvasRef.current) {
   110→      canvasRef.current.width = displaySize.width;
   111→      canvasRef.current.height = displaySize.height;
   112→      // Reset context reference
   113→      ctxRef.current = null;
   114→      frameBufferRef.current = null;
   115→    }
   116→  }, [displaySize]);
   117→
   118→  // Get canvas rect for coordinate translation
   119→  const getCanvasRect = useCallback(() => {
   120→    return canvasRef.current?.getBoundingClientRect() ?? new DOMRect();
   121→  }, []);
   122→
   123→  // Mouse event handlers
   124→  const handleMouseMove = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
   125→    const event = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 0);
   126→    sendMouseEvent(event);
   127→  }, [getCanvasRect, displaySize, sendMouseEvent]);
   128→
   129→  const handleMouseDown = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
   130→    e.preventDefault();
   131→    const event = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 1);
   132→    sendMouseEvent(event);
   133→  }, [getCanvasRect, displaySize, sendMouseEvent]);
   134→
   135→  const handleMouseUp = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
   136→    const event = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 2);
   137→    sendMouseEvent(event);
   138→  }, [getCanvasRect, displaySize, sendMouseEvent]);
   139→
   140→  const handleWheel = useCallback((e: React.WheelEvent<HTMLCanvasElement>) => {
   141→    e.preventDefault();
   142→    const baseEvent = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 3);
   143→    sendMouseEvent({
   144→      ...baseEvent,
   145→      wheelDeltaX: Math.round(e.deltaX),
   146→      wheelDeltaY: Math.round(e.deltaY),
   147→    });
   148→  }, [getCanvasRect, displaySize, sendMouseEvent]);
   149→
   150→  const handleContextMenu = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
   151→    e.preventDefault(); // Prevent browser context menu
   152→  }, []);
   153→
   154→  // Keyboard event handlers
   155→  const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLCanvasElement>) => {
   156→    e.preventDefault();
   157→    const event = createKeyEvent(e, true);
   158→    sendKeyEvent(event);
   159→  }, [sendKeyEvent]);
   160→
   161→  const handleKeyUp = useCallback((e: React.KeyboardEvent<HTMLCanvasElement>) => {
   162→    e.preventDefault();
   163→    const event = createKeyEvent(e, false);
   164→    sendKeyEvent(event);
   165→  }, [sendKeyEvent]);
   166→
   167→  return (
   168→    <div ref={containerRef} className={`remote-viewer ${className}`}>
   169→      <canvas
   170→        ref={canvasRef}
   171→        tabIndex={0}
   172→        onMouseMove={handleMouseMove}
   173→        onMouseDown={handleMouseDown}
   174→        onMouseUp={handleMouseUp}
   175→        onWheel={handleWheel}
   176→        onContextMenu={handleContextMenu}
   177→        onKeyDown={handleKeyDown}
   178→        onKeyUp={handleKeyUp}
   179→        style={{
   180→          width: '100%',
   181→          height: 'auto',
   182→          aspectRatio: `${displaySize.width} / ${displaySize.height}`,
   183→          cursor: 'none', // Hide cursor, remote cursor is shown in frame
   184→          outline: 'none',
   185→          backgroundColor: '#1a1a1a',
   186→        }}
   187→      />
   188→
   189→      {showStatusBar && (
   190→

   199→
   200→            {status.connected ? (
   201→              Connected
   202→            ) : (
   203→              Disconnected
   204→            )}
   205→
   206→          {displaySize.width}x{displaySize.height}
   207→          {status.fps !== undefined && {status.fps} FPS}
   208→          {status.latencyMs !== undefined && {status.latencyMs}ms}
   209→

   210→      )}
   211→

   212→  );
   213→};
   214→
   215→export default RemoteViewer;
   216→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

/**
 * RemoteViewer Component
 *
 * Canvas-based remote desktop viewer that connects to a GuruConnect
 * agent via the relay server. Handles frame rendering and input capture.
 */

import React, { useRef, useEffect, useCallback, useState } from 'react';
import { useRemoteSession, createMouseEvent, createKeyEvent } from '../hooks/useRemoteSession';
import type { VideoFrame, ConnectionStatus } from '../types/protocol';

interface RemoteViewerProps {
  serverUrl: string;
  sessionId: string;
  className?: string;
  onStatusChange?: (status: ConnectionStatus) => void;
  autoConnect?: boolean;
  showStatusBar?: boolean;
}

export const RemoteViewer: React.FC<RemoteViewerProps> = ({
  serverUrl,
  sessionId,
  className = '',
  onStatusChange,
  autoConnect = true,
  showStatusBar = true,
}) => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const containerRef = useRef<HTMLDivElement>(null);
  const ctxRef = useRef<CanvasRenderingContext2D | null>(null);

  // Display dimensions from received frames
  const [displaySize, setDisplaySize] = useState({ width: 1920, height: 1080 });

  // Frame buffer for rendering
  const frameBufferRef = useRef<ImageData | null>(null);

  // Handle incoming video frames
  const handleFrame = useCallback((frame: VideoFrame) => {
    if (!frame.raw || !canvasRef.current) return;

    const { width, height, data } = frame.raw;

    // Update display size if changed
    if (width !== displaySize.width || height !== displaySize.height) {
      setDisplaySize({ width, height });
    }

    // Get or create context
    if (!ctxRef.current) {
      ctxRef.current = canvasRef.current.getContext('2d', {
        alpha: false,
        desynchronized: true,
      });
    }

    const ctx = ctxRef.current;
    if (!ctx) return;

    // For MVP, we assume raw BGRA frames
    // In production, handle compressed frames with fzstd
    let frameData = data;

    // Create or reuse ImageData
    if (!frameBufferRef.current ||
        frameBufferRef.current.width !== width ||
        frameBufferRef.current.height !== height) {
      frameBufferRef.current = ctx.createImageData(width, height);
    }

    const imageData = frameBufferRef.current;

    // Convert BGRA to RGBA for canvas
    const pixels = imageData.data;
    const len = Math.min(frameData.length, pixels.length);

    for (let i = 0; i < len; i += 4) {
      pixels[i] = frameData[i + 2];     // R <- B
      pixels[i + 1] = frameData[i + 1]; // G <- G
      pixels[i + 2] = frameData[i];     // B <- R
      pixels[i + 3] = 255;              // A (opaque)
    }

    // Draw to canvas
    ctx.putImageData(imageData, 0, 0);
  }, [displaySize]);

  // Set up session
  const { status, connect, disconnect, sendMouseEvent, sendKeyEvent } = useRemoteSession({
    serverUrl,
    sessionId,
    onFrame: handleFrame,
    onStatusChange,
  });

  // Auto-connect on mount
  useEffect(() => {
    if (autoConnect) {
      connect();
    }
    return () => {
      disconnect();
    };
  }, [autoConnect, connect, disconnect]);

  // Update canvas size when display size changes
  useEffect(() => {
    if (canvasRef.current) {
      canvasRef.current.width = displaySize.width;
      canvasRef.current.height = displaySize.height;
      // Reset context reference
      ctxRef.current = null;
      frameBufferRef.current = null;
    }
  }, [displaySize]);

  // Get canvas rect for coordinate translation
  const getCanvasRect = useCallback(() => {
    return canvasRef.current?.getBoundingClientRect() ?? new DOMRect();
  }, []);

  // Mouse event handlers
  const handleMouseMove = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
    const event = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 0);
    sendMouseEvent(event);
  }, [getCanvasRect, displaySize, sendMouseEvent]);

  const handleMouseDown = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
    e.preventDefault();
    const event = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 1);
    sendMouseEvent(event);
  }, [getCanvasRect, displaySize, sendMouseEvent]);

  const handleMouseUp = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
    const event = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 2);
    sendMouseEvent(event);
  }, [getCanvasRect, displaySize, sendMouseEvent]);

  const handleWheel = useCallback((e: React.WheelEvent<HTMLCanvasElement>) => {
    e.preventDefault();
    const baseEvent = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 3);
    sendMouseEvent({
      ...baseEvent,
      wheelDeltaX: Math.round(e.deltaX),
      wheelDeltaY: Math.round(e.deltaY),
    });
  }, [getCanvasRect, displaySize, sendMouseEvent]);

  const handleContextMenu = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
    e.preventDefault(); // Prevent browser context menu
  }, []);

  // Keyboard event handlers
  const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLCanvasElement>) => {
    e.preventDefault();
    const event = createKeyEvent(e, true);
    sendKeyEvent(event);
  }, [sendKeyEvent]);

  const handleKeyUp = useCallback((e: React.KeyboardEvent<HTMLCanvasElement>) => {
    e.preventDefault();
    const event = createKeyEvent(e, false);
    sendKeyEvent(event);
  }, [sendKeyEvent]);

  return (
    <div ref={containerRef} className={`remote-viewer ${className}`}>
      <canvas
        ref={canvasRef}
        tabIndex={0}
        onMouseMove={handleMouseMove}
        onMouseDown={handleMouseDown}
        onMouseUp={handleMouseUp}
        onWheel={handleWheel}
        onContextMenu={handleContextMenu}
        onKeyDown={handleKeyDown}
        onKeyUp={handleKeyUp}
        style={{
          width: '100%',
          height: 'auto',
          aspectRatio: `${displaySize.width} / ${displaySize.height}`,
          cursor: 'none', // Hide cursor, remote cursor is shown in frame
          outline: 'none',
          backgroundColor: '#1a1a1a',
        }}
      />

      {showStatusBar && (

            {status.connected ? (
              Connected
            ) : (
              Disconnected
            )}

          {displaySize.width}x{displaySize.height}
          {status.fps !== undefined && {status.fps} FPS}
          {status.latencyMs !== undefined && {status.latencyMs}ms}

      )}

  );
};

export default RemoteViewer;


     1→//! Agent configuration management
     2→
     3→use anyhow::{Context, Result};
     4→use serde::{Deserialize, Serialize};
     5→use std::path::PathBuf;
     6→
     7→/// Agent configuration
     8→#[derive(Debug, Clone, Serialize, Deserialize)]
     9→pub struct Config {
    10→    /// Server WebSocket URL (e.g., wss://connect.example.com/ws)
    11→    pub server_url: String,
    12→
    13→    /// Agent API key for authentication
    14→    pub api_key: String,
    15→
    16→    /// Optional hostname override
    17→    pub hostname_override: Option<String>,
    18→
    19→    /// Capture settings
    20→    #[serde(default)]
    21→    pub capture: CaptureConfig,
    22→
    23→    /// Encoding settings
    24→    #[serde(default)]
    25→    pub encoding: EncodingConfig,
    26→}
    27→
    28→#[derive(Debug, Clone, Serialize, Deserialize)]
    29→pub struct CaptureConfig {
    30→    /// Target frames per second (1-60)
    31→    #[serde(default = "default_fps")]
    32→    pub fps: u32,
    33→
    34→    /// Use DXGI Desktop Duplication (recommended)
    35→    #[serde(default = "default_true")]
    36→    pub use_dxgi: bool,
    37→
    38→    /// Fall back to GDI if DXGI fails
    39→    #[serde(default = "default_true")]
    40→    pub gdi_fallback: bool,
    41→}
    42→
    43→#[derive(Debug, Clone, Serialize, Deserialize)]
    44→pub struct EncodingConfig {
    45→    /// Preferred codec (auto, raw, vp9, h264)
    46→    #[serde(default = "default_codec")]
    47→    pub codec: String,
    48→
    49→    /// Quality (1-100, higher = better quality, more bandwidth)
    50→    #[serde(default = "default_quality")]
    51→    pub quality: u32,
    52→
    53→    /// Use hardware encoding if available
    54→    #[serde(default = "default_true")]
    55→    pub hardware_encoding: bool,
    56→}
    57→
    58→fn default_fps() -> u32 {
    59→    30
    60→}
    61→
    62→fn default_true() -> bool {
    63→    true
    64→}
    65→
    66→fn default_codec() -> String {
    67→    "auto".to_string()
    68→}
    69→
    70→fn default_quality() -> u32 {
    71→    75
    72→}
    73→
    74→impl Default for CaptureConfig {
    75→    fn default() -> Self {
    76→        Self {
    77→            fps: default_fps(),
    78→            use_dxgi: true,
    79→            gdi_fallback: true,
    80→        }
    81→    }
    82→}
    83→
    84→impl Default for EncodingConfig {
    85→    fn default() -> Self {
    86→        Self {
    87→            codec: default_codec(),
    88→            quality: default_quality(),
    89→            hardware_encoding: true,
    90→        }
    91→    }
    92→}
    93→
    94→impl Config {
    95→    /// Load configuration from file or environment
    96→    pub fn load() -> Result<Self> {
    97→        // Try loading from config file
    98→        let config_path = Self::config_path();
    99→
   100→        if config_path.exists() {
   101→            let contents = std::fs::read_to_string(&config_path)
   102→                .with_context(|| format!("Failed to read config from {:?}", config_path))?;
   103→
   104→            let config: Config = toml::from_str(&contents)
   105→                .with_context(|| "Failed to parse config file")?;
   106→
   107→            return Ok(config);
   108→        }
   109→
   110→        // Fall back to environment variables
   111→        let server_url = std::env::var("GURUCONNECT_SERVER_URL")
   112→            .unwrap_or_else(|_| "wss://localhost:3002/ws".to_string());
   113→
   114→        let api_key = std::env::var("GURUCONNECT_API_KEY")
   115→            .unwrap_or_else(|_| "dev-key".to_string());
   116→
   117→        Ok(Config {
   118→            server_url,
   119→            api_key,
   120→            hostname_override: std::env::var("GURUCONNECT_HOSTNAME").ok(),
   121→            capture: CaptureConfig::default(),
   122→            encoding: EncodingConfig::default(),
   123→        })
   124→    }
   125→
   126→    /// Get the configuration file path
   127→    fn config_path() -> PathBuf {
   128→        // Check for config in current directory first
   129→        let local_config = PathBuf::from("guruconnect.toml");
   130→        if local_config.exists() {
   131→            return local_config;
   132→        }
   133→
   134→        // Check in program data directory (Windows)
   135→        #[cfg(windows)]
   136→        {
   137→            if let Ok(program_data) = std::env::var("ProgramData") {
   138→                let path = PathBuf::from(program_data)
   139→                    .join("GuruConnect")
   140→                    .join("agent.toml");
   141→                if path.exists() {
   142→                    return path;
   143→                }
   144→            }
   145→        }
   146→
   147→        // Default to local config
   148→        local_config
   149→    }
   150→
   151→    /// Get the hostname to use
   152→    pub fn hostname(&self) -> String {
   153→        self.hostname_override
   154→            .clone()
   155→            .unwrap_or_else(|| {
   156→                hostname::get()
   157→                    .map(|h| h.to_string_lossy().to_string())
   158→                    .unwrap_or_else(|_| "unknown".to_string())
   159→            })
   160→    }
   161→
   162→    /// Save current configuration to file
   163→    pub fn save(&self) -> Result<()> {
   164→        let config_path = Self::config_path();
   165→
   166→        // Ensure parent directory exists
   167→        if let Some(parent) = config_path.parent() {
   168→            std::fs::create_dir_all(parent)?;
   169→        }
   170→
   171→        let contents = toml::to_string_pretty(self)?;
   172→        std::fs::write(&config_path, contents)?;
   173→
   174→        Ok(())
   175→    }
   176→}
   177→
   178→/// Example configuration file content
   179→pub fn example_config() -> &'static str {
   180→    r#"# GuruConnect Agent Configuration
   181→
   182→# Server connection
   183→server_url = "wss://connect.example.com/ws"
   184→api_key = "your-agent-api-key"
   185→
   186→# Optional: override hostname
   187→# hostname_override = "custom-hostname"
   188→
   189→[capture]
   190→fps = 30
   191→use_dxgi = true
   192→gdi_fallback = true
   193→
   194→[encoding]
   195→codec = "auto"      # auto, raw, vp9, h264
   196→quality = 75        # 1-100
   197→hardware_encoding = true
   198→"#
   199→}
   200→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! Agent configuration management

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// Agent configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    /// Server WebSocket URL (e.g., wss://connect.example.com/ws)
    pub server_url: String,

    /// Agent API key for authentication
    pub api_key: String,

    /// Optional hostname override
    pub hostname_override: Option<String>,

    /// Capture settings
    #[serde(default)]
    pub capture: CaptureConfig,

    /// Encoding settings
    #[serde(default)]
    pub encoding: EncodingConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CaptureConfig {
    /// Target frames per second (1-60)
    #[serde(default = "default_fps")]
    pub fps: u32,

    /// Use DXGI Desktop Duplication (recommended)
    #[serde(default = "default_true")]
    pub use_dxgi: bool,

    /// Fall back to GDI if DXGI fails
    #[serde(default = "default_true")]
    pub gdi_fallback: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncodingConfig {
    /// Preferred codec (auto, raw, vp9, h264)
    #[serde(default = "default_codec")]
    pub codec: String,

    /// Quality (1-100, higher = better quality, more bandwidth)
    #[serde(default = "default_quality")]
    pub quality: u32,

    /// Use hardware encoding if available
    #[serde(default = "default_true")]
    pub hardware_encoding: bool,
}

fn default_fps() -> u32 {
    30
}

fn default_true() -> bool {
    true
}

fn default_codec() -> String {
    "auto".to_string()
}

fn default_quality() -> u32 {
    75
}

impl Default for CaptureConfig {
    fn default() -> Self {
        Self {
            fps: default_fps(),
            use_dxgi: true,
            gdi_fallback: true,
        }
    }
}

impl Default for EncodingConfig {
    fn default() -> Self {
        Self {
            codec: default_codec(),
            quality: default_quality(),
            hardware_encoding: true,
        }
    }
}

impl Config {
    /// Load configuration from file or environment
    pub fn load() -> Result<Self> {
        // Try loading from config file
        let config_path = Self::config_path();

        if config_path.exists() {
            let contents = std::fs::read_to_string(&config_path)
                .with_context(|| format!("Failed to read config from {:?}", config_path))?;

            let config: Config = toml::from_str(&contents)
                .with_context(|| "Failed to parse config file")?;

            return Ok(config);
        }

        // Fall back to environment variables
        let server_url = std::env::var("GURUCONNECT_SERVER_URL")
            .unwrap_or_else(|_| "wss://localhost:3002/ws".to_string());

        let api_key = std::env::var("GURUCONNECT_API_KEY")
            .unwrap_or_else(|_| "dev-key".to_string());

        Ok(Config {
            server_url,
            api_key,
            hostname_override: std::env::var("GURUCONNECT_HOSTNAME").ok(),
            capture: CaptureConfig::default(),
            encoding: EncodingConfig::default(),
        })
    }

    /// Get the configuration file path
    fn config_path() -> PathBuf {
        // Check for config in current directory first
        let local_config = PathBuf::from("guruconnect.toml");
        if local_config.exists() {
            return local_config;
        }

        // Check in program data directory (Windows)
        #[cfg(windows)]
        {
            if let Ok(program_data) = std::env::var("ProgramData") {
                let path = PathBuf::from(program_data)
                    .join("GuruConnect")
                    .join("agent.toml");
                if path.exists() {
                    return path;
                }
            }
        }

        // Default to local config
        local_config
    }

    /// Get the hostname to use
    pub fn hostname(&self) -> String {
        self.hostname_override
            .clone()
            .unwrap_or_else(|| {
                hostname::get()
                    .map(|h| h.to_string_lossy().to_string())
                    .unwrap_or_else(|_| "unknown".to_string())
            })
    }

    /// Save current configuration to file
    pub fn save(&self) -> Result<()> {
        let config_path = Self::config_path();

        // Ensure parent directory exists
        if let Some(parent) = config_path.parent() {
            std::fs::create_dir_all(parent)?;
        }

        let contents = toml::to_string_pretty(self)?;
        std::fs::write(&config_path, contents)?;

        Ok(())
    }
}

/// Example configuration file content
pub fn example_config() -> &'static str {
    r#"# GuruConnect Agent Configuration

# Server connection
server_url = "wss://connect.example.com/ws"
api_key = "your-agent-api-key"

# Optional: override hostname
# hostname_override = "custom-hostname"

[capture]
fps = 30
use_dxgi = true
gdi_fallback = true

[encoding]
codec = "auto"      # auto, raw, vp9, h264
quality = 75        # 1-100
hardware_encoding = true
"#
}


     1→//! Server configuration
     2→
     3→use anyhow::Result;
     4→use serde::Deserialize;
     5→use std::env;
     6→
     7→#[derive(Debug, Clone, Deserialize)]
     8→pub struct Config {
     9→    /// Address to listen on (e.g., "0.0.0.0:8080")
    10→    pub listen_addr: String,
    11→
    12→    /// Database URL (optional for MVP)
    13→    pub database_url: Option<String>,
    14→
    15→    /// JWT secret for authentication
    16→    pub jwt_secret: Option<String>,
    17→
    18→    /// Enable debug logging
    19→    pub debug: bool,
    20→}
    21→
    22→impl Config {
    23→    /// Load configuration from environment variables
    24→    pub fn load() -> Result<Self> {
    25→        Ok(Self {
    26→            listen_addr: env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0:8080".to_string()),
    27→            database_url: env::var("DATABASE_URL").ok(),
    28→            jwt_secret: env::var("JWT_SECRET").ok(),
    29→            debug: env::var("DEBUG")
    30→                .map(|v| v == "1" || v.to_lowercase() == "true")
    31→                .unwrap_or(false),
    32→        })
    33→    }
    34→}
    35→
    36→impl Default for Config {
    37→    fn default() -> Self {
    38→        Self {
    39→            listen_addr: "0.0.0.0:8080".to_string(),
    40→            database_url: None,
    41→            jwt_secret: None,
    42→            debug: false,
    43→        }
    44→    }
    45→}
    46→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! Server configuration

use anyhow::Result;
use serde::Deserialize;
use std::env;

#[derive(Debug, Clone, Deserialize)]
pub struct Config {
    /// Address to listen on (e.g., "0.0.0.0:8080")
    pub listen_addr: String,

    /// Database URL (optional for MVP)
    pub database_url: Option<String>,

    /// JWT secret for authentication
    pub jwt_secret: Option<String>,

    /// Enable debug logging
    pub debug: bool,
}

impl Config {
    /// Load configuration from environment variables
    pub fn load() -> Result<Self> {
        Ok(Self {
            listen_addr: env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0:8080".to_string()),
            database_url: env::var("DATABASE_URL").ok(),
            jwt_secret: env::var("JWT_SECRET").ok(),
            debug: env::var("DEBUG")
                .map(|v| v == "1" || v.to_lowercase() == "true")
                .unwrap_or(false),
        })
    }
}

impl Default for Config {
    fn default() -> Self {
        Self {
            listen_addr: "0.0.0.0:8080".to_string(),
            database_url: None,
            jwt_secret: None,
            debug: false,
        }
    }
}