Luna is a self-hosted virtual assistant for homelabs. It routes natural-language commands to registered devices (agents), executes local actions on them, and returns structured success/failure feedback to a web UI. Current status: MVP through Slice 52 (resilient agent reconnect runtime).
- Language: TypeScript, Node.js 20+ (npm workspaces monorepo)
- Server: Node.js, WebSocket (
ws), REST, UDP discovery - Web: Next.js
- Agent runtime: Node.js device executor, publishable as a standalone CLI (
@vitorqf/luna-agent) - Validation: Zod
- Testing: Vitest
- Packaging: Docker (embedded server + web artifact)
- Natural-language command parsing (rule-based, targets Portuguese phrasing) for
open_app,notify,set_volume, andplay_media - Command dispatch to registered devices over WebSocket, with local execution and structured success/failure responses (
invalid_params,unsupported_intent,execution_error) - Device presence tracking (
online/offline) with heartbeat timeout and safe reconnect handling for the same device id - Agent discovery via UDP announce, plus a REST-based approval flow before a discovered agent becomes a known device
- Capability-based dispatch validation before sending a command to a device
- In-memory command history exposed via REST
- Publishable agent CLI package and a Docker image bundling server + embedded web
flowchart LR
UI["Luna Web (Next.js)"] -->|REST| S["Luna Server (Node.js + WS + HTTP)"]
S -->|WebSocket command.dispatch| A["Luna Agent (Node.js)"]
A -->|WebSocket command.ack| S
A -->|UDP announce| S
S -->|REST data| UI
Monorepo layout:
apps/
server/ # REST + WebSocket + discovery runtime
web/ # Next.js chat/dashboard UI
agent/ # Device runtime and local command executors
packages/
shared-types/ # Cross-app domain types
protocol/ # WebSocket and UDP message contracts
command-parser/ # Rule-based natural-language parser
- Node.js 20+
- npm
npm install
cp .env.example .envKey environment variables (see .env.example for the full list):
| Variable | Description |
|---|---|
LUNA_SERVER_HOST / LUNA_SERVER_PORT |
Server bind address (default 127.0.0.1:4000) |
LUNA_AGENT_SERVER_URL |
WebSocket URL the agent connects to |
LUNA_AGENT_DEVICE_ID / _NAME / _HOSTNAME |
Device identity used by the agent |
NEXT_PUBLIC_LUNA_SERVER_URL |
Base URL used by the web app (empty for same-origin embedded builds) |
# Terminal 1
npm run start:server
# Terminal 2
npm run start:agent
# Terminal 3
npm run start:webOpen the web UI at http://127.0.0.1:3000.
npm run docker:build:server
npm run docker:run:serverServes both from http://127.0.0.1:4000.
npm run test
npm run typecheck| Method | Path | Purpose |
|---|---|---|
GET |
/devices |
List registered devices |
GET |
/commands |
List command history |
GET |
/discovery/agents |
List discovered (not yet approved) agents |
POST |
/commands |
Submit a natural-language command ({ "rawText": "..." }) |
PATCH |
/devices/:id |
Rename a device alias |
POST |
/discovery/agents/:id/approve |
Approve a discovered agent as a known device |
Follow AGENTS.md (TDD-first, one slice at a time). Use Conventional Commits, e.g. feat(agent): add executable packaging bootstrap.