Initializing
Back to Projects
Year2025
DomainFullstack
AccessOpen Source
Complexity0 / 10
HonoNode.jsSQLiteDrizzle ORMViteReactWebSocketLSP-BridgeMonorepoPWA
FullstackArchived

GATOR Lite (APEX) - AI Orchestration System

A lightweight, ultra-fast personal AI orchestration system that provides chat-first, intent-based remote control over IDE workspaces from any device via mobile.

Parsing system architecture diagram...

Technology Stack

LayerTechnologyVersionJustification
Web FrameworkHono4.xFastest framework (2-3x Express, 5x NestJS), tiny bundle, zero magic
RuntimeNode.js20+ LTSESM-native, stable
DatabaseSQLite (better-sqlite3)latestEmbedded, zero setup, WAL mode for concurrent reads
ORMDrizzle ORM0.30+TypeScript-first, zero-runtime overhead
Real-timeSocket.IO4.xBattle-tested WebSocket with rooms and reconnect logic
Task QueueCustom In-Memory Priority Queue-For single-process, Map<priority, Queue> sufficient
FrontendVite5.xLightning-fast HMR (<50ms), 3-5x smaller than Next.js
UI FrameworkReact18.xDeclarative, concurrent, stable
StateZustand4.xMinimal, hooks-first, no boilerplate
Server StateTanStack Query5.xIntelligent caching and background refetch
StylingVanilla CSS + CSS Modules-Custom glassmorphism design system
OfflineIndexedDB (idb)8.xOffline command queue and cursor persistence
Monorepopnpm workspaceslatestPackage manager + workspace orchestration
TestingVitestlatestFaster than Jest, native ESM support

Purpose and Philosophy

GATOR Lite (codename APEX) is the lean, ultra-fast successor to GATOR v1. It is a private, AI-powered orchestration system that provides chat-first, intent-based remote control over your Antigravity IDE workspace from any device — primarily Android mobile.

The core philosophy centers on "Phone as Control Surface Only" — the mobile client has no direct host access. All execution routes through the core → LSP-Bridge chain. The phone emits intent, never commands.

Core Design Principles

  1. UI is a Lens, Not Source of Truth: The APEX Core owns all state. Clients read snapshots and emit intent. The UI never computes business logic locally.
  1. Every Action is a Named Whitelisted Task: task-registry.json is the security contract. Unknown actions are rejected before execution.
  1. LSP-Bridge, Never UI Scraping: All IDE communication goes through PORTA's verified Connect RPC protocol. No CDP, no DOM automation, no UI pixel-scraping. 100% ToS-compliant.
  1. Network Exposure Guard: The Core binds only to loopback, private LAN, or Tailscale CGNAT. Wildcard binds (0.0.0.0) throw at startup.
  1. Offline First: IndexedDB queue on client. SQLite WAL persists events. Tasks survive Core restarts.
  1. Zero-Config Startup: No Docker. No database setup. Run pnpm dev and the system is live with a self-contained SQLite file.
  1. Lean by Discipline: Before adding any dependency: does this justify its weight? If a built-in Node.js API can do it, no library is added.

Architecture Deep Dive

System Topology

The entire system runs as one lightweight Hono process with embedded SQLite:

code
┌─────────────────────────────────────────────────────────────────────┐
APEX CORE (HonoSingle Process)
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐           │
│  │   Auth       │  │ Chat Router  │  │  WS Server   │           │
│  │   (JWT/OTP)  │  │ + Intent     │  │ (Socket.IO)  │           │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘           │
│         │                 │                  │                    │
│  ┌──────▼─────────────────▼──────────────────▼────────────────────┐ │
│  │            Orchestrator (Task Lifecycle)                    │ │
│  │   validatededuplicateenqueuetracknotify       │ │
│  └───────────────────────┬─────────────────────────────────────┘ │
│                          │                                       │
│  ┌────────────────┐  ┌─┴───────────────────────┐                │
│  │  SQLite (WAL) │  │ In-Memory Queue       │                │
│  │  (Drizzle ORM) │  │ low | med | high      │                │
│  └────────────────┘  └───────────────────────┘                │
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  LSP-BRIDGE (PORTAembedded in Core)                  │    │
│  │  ls-discoveryRPC ClientDelta-PollingStep Recovery│    │
│  └─────────────────────────────────────────────────────────────┘    │
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  Execution Sandbox (child_process.spawn)                  │    │
│  │  Whitelisted commands only | restricted PATH              │    │
│  └─────────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────┘

Database Schema (SQLite + Drizzle)

typescript
// packages/db/schema.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';

// Devices
export const devices = sqliteTable('devices', {
  id:             text('id').primaryKey(),
  name:           text('name').notNull(),
  platform:       text('platform').notNull(),
  jwtFingerprint: text('jwt_fingerprint').notNull().unique(),
  pairedAt:       integer('paired_at', { mode: 'timestamp' }).notNull(),
  lastSeen:       integer('last_seen', { mode: 'timestamp' }).notNull(),
  role:           text('role').notNull().default('operator'),
  isActive:       integer('is_active', { mode: 'boolean' }).notNull().default(true),
});

// Tasks
export const tasks = sqliteTable('tasks', {
  id:            text('id').primaryKey(),
  deviceId:      text('device_id').notNull(),
  action:        text('action').notNull(),
  target:        text('target'),
  status:        text('status').notNull(),
  riskLevel:     text('risk_level'),
  resultSummary: text('result_summary'),
  exitCode:      integer('exit_code'),
  durationMs:    integer('duration_ms'),
  submittedAt:   integer('submitted_at', { mode: 'timestamp' }).notNull(),
  startedAt:     integer('started_at', { mode: 'timestamp' }),
  completedAt:    integer('completed_at', { mode: 'timestamp' }),
});

// Conversations
export const conversations = sqliteTable('conversations', {
  cascadeId:      text('cascade_id').primaryKey(),
  workspaceId:    text('workspace_id').notNull(),
  threadId:       text('thread_id').unique(),
  title:          text('title'),
  stepCount:      integer('step_count').notNull().default(0),
  status:         text('status').notNull().default('idle'),
  lastSyncedAt:   integer('last_synced_at', { mode: 'timestamp' }),
  createdAt:      integer('created_at', { mode: 'timestamp' }).notNull(),
  updatedAt:       integer('updated_at', { mode: 'timestamp' }).notNull(),
});

// Settings
export const settings = sqliteTable('settings', {
  key:   text('key').primaryKey(),
  value: text('value').notNull(),
});

// JWT Blocklist
export const jwtBlocklist = sqliteTable('jwt_blocklist', {
  fingerprint: text('fingerprint').primaryKey(),
  revokedAt:   integer('revoked_at', { mode: 'timestamp' }).notNull(),
  expiresAt:   integer('expires_at', { mode: 'timestamp' }).notNull(),
});

Intent Parser

typescript
// Rule-based intent parsing
interface ParsedIntent {
  action: string;
  target: string;
  confidence: number;
  riskLevel: 'low' | 'medium' | 'high';
}

// Example mappings
const intentRules = [
  { pattern: /run\s+tests?\s+on\s+(\w+)/i, action: 'run_tests', target: '$1' },
  { pattern: /build\s+(\w+)/i, action: 'build_project', target: '$1' },
  { pattern: /deploy/i, action: 'deploy', target: 'production' },
];

function parseIntent(message: string): ParsedIntent {
  for (const rule of intentRules) {
    const match = message.match(rule.pattern);
    if (match) {
      const target = rule.target.replace('$1', match[1] || '');
      return {
        action: rule.action,
        target,
        confidence: 0.97,
        riskLevel: getRiskLevel(rule.action),
      };
    }
  }
  return { action: 'unknown', target: '', confidence: 0 };
}

Task Orchestrator

typescript
// packages/orchestrator/task-orchestrator.ts

async function processIntent(deviceId: string, message: string) {
  // 1. Parse intent
  const intent = parseIntent(message);
  
  // 2. Validate against task registry
  const taskDef = taskRegistry[intent.action];
  if (!taskDef) {
    throw new Error('Action not permitted');
  }
  
  // 3. Generate deterministic task ID
  const taskId = sha256(`${deviceId}${intent.action}${intent.target}${Date.now()}`);
  
  // 4. Check for duplicates (dedup)
  const existing = await getTaskFromQueue(taskId);
  if (existing) return { taskId, status: 'duplicate' };
  
  // 5. Enqueue with priority
  const priority = taskDef.riskLevel === 'high' ? 'high' : 
                   taskDef.riskLevel === 'medium' ? 'medium' : 'low';
  await enqueueTask({ taskId, ...intent, priority, deviceId });
  
  // 6. Persist to SQLite
  await db.insert(tasks).values({
    id: taskId,
    deviceId,
    action: intent.action,
    target: intent.target,
    status: 'queued',
    riskLevel: taskDef.riskLevel,
    submittedAt: new Date(),
  });
  
  return { taskId, status: 'queued' };
}

Execution Sandbox

typescript
// packages/task-runner/sandbox.ts

const ALLOWED_BINARIES = ['node', 'npm', 'npx', 'pnpm', 'git', 'docker', 'pm2'];
const RESTRICTED_PATH = '/usr/local/bin:/usr/bin:/bin';

async function executeTask(task: QueuedTask) {
  const command = taskRegistry[task.action].command;
  const args = resolveArgs(task.action, task.target);
  
  return new Promise((resolve, reject) => {
    const child = spawn(command, args, {
      cwd: resolveWorkspacePath(task.target),
      env: {
        PATH: RESTRICTED_PATH,
        HOME: process.env.HOME,
        NODE_ENV: 'production',
      },
      uid: process.getuid(), // non-root assertion
      stdio: ['ignore', 'pipe', 'pipe'],
    });
    
    child.stdout.on('data', (chunk) => {
      // Stream to WebSocket
      ws.to(task.id).emit('task:log', { chunk, seq: seq++ });
    });
    
    child.stderr.on('data', (chunk) => {
      ws.to(task.id).emit('task:log', { chunk: `[ERR] ${chunk}`, seq: seq++ });
    });
    
    child.on('close', (code) => {
      resolve({ exitCode: code, durationMs: Date.now() - start });
    });
  });
}

LSP-Bridge Integration

The system integrates with the Antigravity Language Server via Connect RPC:

typescript
// packages/lsp-bridge/rpc.ts

interface LSInstance {
  pid: number;
  httpsPort: number;
  csrfToken: string;
  workspaceId: string;
}

// Connect RPC call
async function callLSMethod(method: string, payload: unknown, instance: LSInstance) {
  const response = await fetch(
    `https://127.0.0.1:${instance.httpsPort}/exa.language_server_pb.LanguageServerService/${method}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-codeium-csrf-token': instance.csrfToken,
      },
      body: JSON.stringify(payload),
      rejectUnauthorized: false,
    }
  );
  
  return response.json();
}

// Start AI conversation
async function startCascade(workspaceId: string, message: string) {
  const instance = await discoverLS(workspaceId);
  return callLSMethod('StartCascade', {
    workspaceId,
    messages: [{ role: 'user', content: message }],
    metadata: { ideName: 'gator-lite', ideVersion: '1.0.0' },
  }, instance);
}

// Send message to running conversation
async function sendMessage(cascadeId: string, message: string) {
  return callLSMethod('SendUserCascadeMessage', {
    cascadeId,
    items: [{ text: message }],
  }, instance);
}

Delta Polling State Machine

typescript
// packages/lsp-bridge/delta-poller.ts

const ACTIVE_INTERVAL = 150;   // ms (reduced from 50ms in v1)
const IDLE_INTERVAL = 20000;    // ms (increased from 15000ms)
const EMPTY_THRESHOLD = 5;       // consecutive empty polls before IDLE
const OVERLAP_COUNT = 5;       // trailing steps re-fetched per poll

let state = 'IDLE';
let consecutiveEmpty = 0;

async function poll() {
  const steps = await fetchSteps(cascadeId, offset);
  
  if (steps.length > 0) {
    consecutiveEmpty = 0;
    socket.to(cascadeId).emit('cascade:steps', { offset, steps });
    offset += steps.length;
  } else {
    consecutiveEmpty++;
    if (consecutiveEmpty >= EMPTY_THRESHOLD) {
      state = 'IDLE';
    }
  }
}

setInterval(() => {
  if (state === 'ACTIVE') poll();
}, state === 'ACTIVE' ? ACTIVE_INTERVAL : IDLE_INTERVAL);

// Activate on user interaction
function activate() {
  state = 'ACTIVE';
  consecutiveEmpty = 0;
  poll();
}

Security Architecture

OTP Pairing Flow

typescript
// 1. Desktop opens /api/auth/otp (desktop-only)
const otp = generateOTP(); // 6-digit
await db.insert(otps).values({ code: otp, expiresAt: now + 5min });

// 2. Phone submits POST /api/auth/pair
const device = await db.select().from(devices).where(eq(otps.code, otp));
if (!device) throw new Error('Invalid OTP');

// 3. Core creates device and signs JWT
const token = await signJWT({ sub: device.id, fingerprint: sha256(token) });
return { token, expiresAt: now + 30d };

Network Exposure Guard

typescript
// Block wildcards at startup
const allowed = ['127.0.0.1', 'localhost', '::1', '10.x.x.x', '172.16-31.x.x', '192.168.x.x'];
if (process.env.PORTA_TAILSCALE) allowed.push('100.64.x.x');

if (!allowed.includes(CORE_HOST)) {
  throw new Error('Public internet exposure unsupported');
}

API Endpoints

Auth & Devices

MethodPathAuthDescription
POST/api/auth/pairNoneSubmit OTP, receive JWT
POST/api/auth/refreshJWTRefresh JWT
GET/api/auth/otpDesktopGenerate pairing OTP
GET/api/devicesJWTList paired devices
DELETE/api/devices/:idJWTRevoke device

Chat & Tasks

MethodPathAuthDescription
POST/api/chatJWTSubmit intent message
GET/api/tasksJWTList recent tasks
GET/api/tasks/:id/logsJWTGet full log output
POST/api/tasks/:id/confirmJWTConfirm high-risk task

Conversations

MethodPathAuthDescription
GET/api/conversationsJWTList all cascades
POST/api/conversations/:id/messagesJWTSend message
POST/api/conversations/:id/stopJWTCancel cascade

WebSocket Protocol

Server → Client events:

typescript
socket.on('task:queued',    { taskId, action, target, riskLevel })
socket.on('task:running',    { taskId })
socket.on('task:log',        { taskId, chunk, seq })
socket.on('task:complete',    { taskId, status, exitCode, durationMs })
socket.on('cascade:steps',     { cascadeId, offset, steps })
socket.on('cascade:status',   { cascadeId, running })
socket.on('system:alert',      { level, message })

Frontend Architecture

Design System - "Glass & Glow"

css
:root {
  --color-bg-base:    hsl(220, 20%, 6%);
  --color-bg-surface: hsl(220, 18%, 10%);
  --color-bg-glass:   hsla(220, 18%, 15%, 0.6);
  --color-accent:    hsl(164, 80%, 50%);
  --glass-blur:      16px;
  --glass-border:    1px solid hsla(220, 18%, 50%, 0.15);
  --font-ui:         'Inter', system-ui, sans-serif;
  --font-mono:       'JetBrains Mono', monospace;
}

PWA Configuration

json
{
  "name": "GATOR Lite",
  "short_name": "GATOR",
  "display": "standalone",
  "start_url": "/",
  "background_color": "#0d0f17",
  "theme_color": "#0d0f17"
}

Key Features

  1. Chat-First Intent Control: Type natural language on phone, see real-time steps stream back
  2. Task Execution: Run whitelisted shell tasks from anywhere
  3. Live Log Streaming: Real-time stdout/stderr via WebSocket
  4. Conversation Management: Browse, resume, revert, delete AI conversations
  5. Session Audit: Every action recorded, every device tracked
  6. Offline Queue: Commands survive connection drops and replay in order
  7. Delta Polling: 150ms intervals when active, 20s when idle
  8. Step Recovery: 3-tier recovery for corrupted steps

Performance Comparison

MetricGATOR v1GATOR Lite
RAM Usage~720MB<150MB
Startup Time~30s<1.5s
Cold Start~500ms~50ms
Bundle Size~250KB~50KB
Docker Containers50

Build and Run

bash
# Install dependencies
pnpm install

# Create env file
cp .env.example .env
# Edit: JWT_SECRET, CORE_HOST

# Start all services (Core + Web in parallel)
pnpm dev

# Core: tsx --watch apps/core/src/main.ts (port 4000)
# Web: vite apps/web (port 5173, proxied to :4000)

Conclusion

GATOR Lite represents a complete redesign of the original GATOR system, optimized for personal single-user deployment. Key achievements include:

  • Ultra-lightweight architecture (150MB RAM vs 720MB)
  • Single-process design with embedded SQLite
  • Hono framework for maximum performance
  • Complete LSP-Bridge integration with Antigravity IDE
  • Offline-first with IndexedDB queue
  • Security-first with whitelist enforcement and network exposure guards
  • Premium glassmorphism UI design

The system is designed for developers who want private, secure, ultra-fast remote control of their development environment from mobile devices.

(End of file - 605 lines)

Architecture Feedback

Spotted a potential optimization or antipattern? Let me know.

Submit a Technical Suggestion