{"id":50373375,"url":"https://github.com/vardhin/lexicon","last_synced_at":"2026-05-30T08:04:21.974Z","repository":{"id":340429485,"uuid":"1165927886","full_name":"vardhin/lexicon","owner":"vardhin","description":null,"archived":false,"fork":false,"pushed_at":"2026-03-06T05:08:02.000Z","size":1725,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-06T09:50:59.192Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Svelte","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vardhin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-24T17:37:28.000Z","updated_at":"2026-03-06T05:08:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/vardhin/lexicon","commit_stats":null,"previous_names":["vardhin/lexicon"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/vardhin/lexicon","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vardhin%2Flexicon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vardhin%2Flexicon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vardhin%2Flexicon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vardhin%2Flexicon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vardhin","download_url":"https://codeload.github.com/vardhin/lexicon/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vardhin%2Flexicon/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33684419,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-30T08:04:21.867Z","updated_at":"2026-05-30T08:04:21.956Z","avatar_url":"https://github.com/vardhin.png","language":"Svelte","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"architecture/Mermaid Chart - Create complex, visual diagrams with text.-2026-02-24-134541.png\" width=\"700\" alt=\"LSD Architecture\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eLSD — Lexicon Shell Daemon\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  A transparent, fullscreen overlay OS layer for Linux — triggered by a hotkey, driven by natural language, rendered as floating glass widgets.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cem\u003e\"It's a shell that lives on top of your entire screen — like a heads-up display for your desktop. You press a key, a transparent layer appears, you type what you want in plain English, and it just… does it.\"\u003c/em\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#what-is-lsd\"\u003eWhat is LSD?\u003c/a\u003e •\n  \u003ca href=\"#architecture\"\u003eArchitecture\u003c/a\u003e •\n  \u003ca href=\"#status\"\u003eStatus\u003c/a\u003e •\n  \u003ca href=\"#getting-started\"\u003eGetting Started\u003c/a\u003e •\n  \u003ca href=\"#adding-extensions\"\u003eAdding Extensions\u003c/a\u003e •\n  \u003ca href=\"#widget-conventions\"\u003eWidget Conventions\u003c/a\u003e •\n  \u003ca href=\"#entity-resolution\"\u003eEntity Resolution\u003c/a\u003e •\n  \u003ca href=\"#automations\"\u003eAutomations\u003c/a\u003e •\n  \u003ca href=\"#roadmap\"\u003eRoadmap\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## What is LSD?\n\n**LSD** stands for **Lexicon Shell Daemon** — but in plain terms, it's a see-through layer that sits on top of your entire Linux desktop. Think of it like a HUD in a video game, except it's for your real computer.\n\nHere's what that means in practice:\n\n1. **You press a hotkey** (like `Super + `` `) and a transparent fullscreen window instantly appears over whatever you were doing — your browser, your code editor, everything stays visible underneath.\n2. **You type in plain English** into a bar at the bottom (the \"Synapse Bar\"): things like `\"clock\"`, `\"timer 5m\"`, `\"what's the date\"`, `\"theme cyberpunk\"`, or even shell commands like `ls` and `git status`.\n3. **Floating glass widgets appear** on the overlay — a clock, a timer, a sticky note, a system monitor — whatever you asked for.\n4. **Press Escape** and the whole thing vanishes. Zero CPU when hidden. Your desktop is untouched.\n\nIt's not a terminal emulator. It's not an app launcher. It's a **daemon** (a background process) that gives your desktop a programmable glass nervous system you can talk to in natural language.\n\nThe name is a triple meaning:\n- **L**exicon **S**hell **D**aemon — what it literally is\n- **LSD** — because it puts a trippy transparent layer over your reality\n- It's also just a daemon (`lexicond`) — it runs silently in the background, always listening\n\n---\n\n## Architecture\n\nLSD is a multi-layer system with body-inspired naming:\n\n| Layer | Name | Tech | Role |\n|-------|------|------|------|\n| **0** | **The Body** | Tauri + Bun + SvelteKit | Transparent fullscreen window, Rust IPC, WebView rendering |\n| **0+** | **Organs** | Playwright ghost browser + DOM scraping + automation | Real web apps (GitHub, WhatsApp, etc.) as headless tabs — scraped and automated, not embedded |\n| **1** | **The Brain** | Python + FastAPI + uv | Rule-based grammar engine, WebSocket hub, extension loader, organ orchestration, automation executor |\n| **1+** | **The Shell** | Python PTY microservice | Real pseudo-terminal sessions (zsh/bash), full interactive shell over WebSocket |\n| **2** | **The Spine** | ZeroMQ (pyzmq) | PUSH/PULL + PUB event bus between all layers |\n| **3** | **The Memory** | SurrealDB (embedded) | Persistent document storage — UI state, history, workspaces, scraped data, automations, themes |\n| **4** | **External Sensors** | CLI scripts, daemons | System monitors, ad-hoc data pushers via Spine |\n\n### Data Flow\n\n```\nBoot                  →  dev.sh starts Brain + Shell + Spine + Tauri client\n                      →  Tauri window opens briefly (WebView boots, JS connects)\n                      →  Svelte connects to WebSocket (ws://127.0.0.1:8000/ws)\n                      →  Brain sends RESTORE_STATE (widgets) + active theme from Memory\n                      →  Rust auto-hides the window after 2s (WebView stays alive)\n                      →  LSD is now idle — zero CPU, no taskbar icon\n\nUser presses Super+`  →  lexicon-toggle PUSHes \"lexicon/toggle\" to Spine (:5557)\n                      →  Spine dispatches to Brain handler\n                      →  Brain broadcasts TOGGLE_VISIBILITY over WebSocket\n                      →  Svelte calls invoke(\"toggle_window\") → Rust IPC\n                      →  Rust shows window + sets fullscreen + focuses\n                      →  Saved widgets reappear instantly (already in memory)\n\nUser presses Escape   →  Svelte calls invoke(\"toggle_window\") → Rust hides window\n                      →  Window vanishes instantly, WebView stays connected\n\nUser types \"clock\"    →  Svelte sends { type: \"query\", text: \"clock\" } via WebSocket\n                      →  Brain logs command to Memory, runs GrammarEngine\n                      →  extensions/clock.py match() hits → action() returns RENDER_WIDGET\n                      →  Sent back over WebSocket\n                      →  Svelte looks up registry[\"clock\"], renders \u003cClockWidget\u003e\n                      →  Widget appears at (x, y) with glass blur frame\n\nUser types \"!ls\"      →  Svelte detects shell prefix, sends shell_spawn + shell_input\n                      →  Brain relays to Shell microservice (ws://127.0.0.1:8765)\n                      →  Shell service spawns real PTY zsh session\n                      →  Output streams back: Shell → Brain → WebSocket → xterm.js widget\n                      →  Full-screen terminal widget with colors, scrollback, resize\n\nUser types            →  Svelte sends { type: \"apply_theme\", name: \"cyberpunk\" }\n  \"theme cyberpunk\"   →  Brain looks up CSS from Memory (SurrealDB)\n                      →  Broadcasts APPLY_THEME { css: \"...\" } to ALL connected clients\n                      →  Svelte injects \u003cstyle id=\"lexicon-theme\"\u003e into \u003chead\u003e\n                      →  Every widget, bar, sidebar re-skins instantly via lx-* classes\n\nUser types \"organs\"   →  OrganManagerWidget spawns on canvas\n                      →  Register any URL as an \"organ\" (e.g. github.com, web.whatsapp.com)\n                      →  Brain's OrganManager opens a tab in a Playwright ghost browser\n                      →  User pastes outer HTML of a page element → names it → scrapes\n                      →  Playwright deep-scrapes all matching elements with field extraction\n                      →  Structured data stored in Memory, viewable in DataViewWidget\n\nUser types            →  AutomationWidget spawns on canvas\n  \"automations\"       →  Select an organ (must be running)\n                      →  Build step sequences: click, scroll, type, wait, navigate, extract\n                      →  Save automation → run it → watch step-by-step progress\n                      →  Extracted data auto-stored in Memory, auto-resolved to entities\n                      →  One-shot actions: click/type/scroll any element, take screenshots\n```\n\n---\n\n## Status\n\n\u003e **Current phase: All core layers functional — Body + Brain + Shell + Spine + Organs + Automations + Theming.**\n\n### ✅ Implemented\n\n| Component | Status | Details |\n|-----------|--------|---------|\n| **Tauri shell (Layer 0)** | ✅ Complete | Transparent, borderless, always-on-top, fullscreen. Boots visible (WebView boots + WebSocket connects), Rust auto-hides after 2s. Toggle via `lexicon-toggle` (ZeroMQ → Spine → Brain → WS → Svelte → Rust IPC). Escape hides. Builds to ~16MB release binary. |\n| **Svelte frontend (Layer 0)** | ✅ Complete | SPA with static adapter, frost-glass overlay, Synapse Bar with command history (↑↓), feedback toasts, connection status dot, `lx-*` CSS anchor classes for theming. |\n| **Paged workspace (Layer 0)** | ✅ Complete | Vertically scrolling canvas divided into pages by thin divider lines. Sidebar with page numbers for smooth scroll navigation. Auto-expands as content grows. Widgets freely span across dividers. |\n| **Widget system (Layer 0)** | ✅ Complete | Dynamic render list driven by WebSocket. Absolute positioning at `(x, y, w, h)`. Glass-blur frames, pop-in animation, per-widget dismiss. Pointer-based dragging via handle strip. Corner resize handle. All positions/sizes persist to Memory. |\n| **Widget registry (Layer 0)** | ✅ Complete | `src/lib/widgets/index.js` — maps `widget_type` → Svelte component. 13 widgets: clock, timer, date, note, calculator, sysmon, weather, help, terminal, organmanager, dataview, person, automation. |\n| **Multi-session shell (Layer 0+1)** | ✅ Complete | Full PTY shell via dedicated Shell microservice (:8765). Multiple concurrent sessions — each is a real zsh/bash PTY with colors, env persistence, interactive programs. Rendered in xterm.js TerminalWidgets on the canvas. Ctrl+C, resize, signals all work natively. Synapse Bar routes to active session or spawns new ones (Ctrl+\\`, Ctrl+Tab). |\n| **Workspaces (Layer 0+3)** | ✅ Complete | Named workspaces in SurrealDB. ✦ logo → workspace menu: create, switch, delete. Each workspace has independent widgets, shell state. 🧹 clear button wipes canvas + DB. Auto-saves on switch, auto-restores on load. |\n| **WebSocket protocol (Layer 0↔1)** | ✅ Complete | Auto-reconnect with exponential backoff (2s → 30s). Message types: `RENDER_WIDGET`, `REMOVE_WIDGET`, `CLEAR_WIDGETS`, `CLEAR_SHELL`, `FEEDBACK`, `RESTORE_STATE`, `SHELL_SPAWNED`, `SHELL_OUTPUT`, `SHELL_EXITED`, `SHELL_ERROR`, `WORKSPACE_INFO`, `TOGGLE_VISIBILITY`, `ORGAN_STATUS`, `ORGAN_LIST`, `APPLY_THEME`, `THEME_LIST`, `THEME_INFO`, `AUTOMATION_PROGRESS`. |\n| **FastAPI Brain (Layer 1)** | ✅ Complete | WebSocket at `/ws`, health at `/health`, toggle at `POST /toggle`, system stats at `/system`. Organ CRUD: `POST/GET/DELETE /organs`, `/organs/:id/launch`, `/organs/:id/kill`, `/organs/:id/match`, `/organs/:id/scrape`, `/organs/:id/rescrape`, `/organs/:id/data`. Automation CRUD: `POST/GET/DELETE /organs/:id/automations`, `/organs/:id/automations/:name/run`. One-shot actions: `/organs/:id/actions/{click,type,scroll,navigate,screenshot,eval,extract,paginate}`. Entity endpoints: `GET /entities`, `GET /entities/:id`, `GET /entities/search/:q`, `POST /entities/resolve`, `DELETE /entities`, `GET /entities/stats/summary`. Connection manager with broadcast. CORS enabled. Workspace CRUD + theme CRUD over WebSocket. |\n| **Grammar engine (Layer 1)** | ✅ Complete | Dynamically loads every `.py` from `extensions/`, runs `match()` → `action()` pipeline. 15 extensions loaded. Fallback feedback for unknown commands. Help entries auto-collected. |\n| **Shell microservice (Layer 1+)** | ✅ Complete | Standalone Python PTY server on `:8765`. Auto-detects user's default shell. Real PTY with `TIOCSWINSZ` resize, `SIGHUP`/`SIGINT`/`SIGTSTP` signals. Raw byte streaming. Multiple concurrent sessions. |\n| **Organ system (Layer 0+)** | ✅ Complete | **Generic organ framework** — any URL can be an organ. Single headed Playwright Chromium browser (off-screen, persistent cookies). Organs are tabs. **Deep structural scraping**: paste outer HTML → tree parser discovers fields → CSS selector extraction → structured objects per match. 3-stage pipeline: similarity → structural validation → deduplication. OrganManagerWidget for CRUD. DataViewWidget for recursive layout rendering. |\n| **Automation engine (Layer 0+1)** | ✅ Complete | **Programmable browser automation** — goes beyond static scraping. Build named step sequences: `click`, `type`, `scroll`, `wait`, `navigate`, `extract`, `paginate`, `eval_js`, `screenshot`, `conditional`, `loop`. Saved to Memory per organ. Run automations with step-by-step progress broadcast. One-shot actions for ad-hoc interaction. Paginate across multiple pages with auto-extraction. Extracted data auto-stored and auto-resolved to entity nodes. AutomationWidget for visual workflow building + execution. |\n| **Entity resolver (Layer 1+3)** | ✅ Complete | **Multi-strategy consensus identity resolution** — zero external dependencies (except spaCy NER). Scraped data from any organ is automatically resolved into person-entity nodes. 5 voting strategies: fingerprint (phone/email exact), name similarity (Jaro-Winkler + Soundex + Double Metaphone), username correlation (cross-platform handle matching), token overlap (IDF-weighted + substring containment), contextual (avatar URL + phone digit matching). Only applicable strategies vote — absent signals don't dilute strong matches. Entities stored in SurrealDB with full source provenance. |\n| **Person widget (Layer 0)** | ✅ Complete | PersonWidget — searchable grid of all resolved people, click-to-expand detail profiles with avatar, name, aliases, handles, contact info, source provenance. Built entirely from DataView meta node primitives. Full `lx-person-*` theme anchors. Search via `\"people\"`, `\"person Rishi\"`, `\"who is Mehta\"`, `\"contacts\"`. |\n| **Theming (Layer 0+3)** | ✅ Complete | Full theme system with CSS injection. 4 built-in themes: `cyberpunk`, `midnight`, `rose-pine`, `ember`. Themes stored in SurrealDB Memory, auto-seeded from `themes/*.css` on boot. Apply via natural language (`\"theme cyberpunk\"`), WebSocket messages, or Spine channel (`lexicon/theme`). Active theme persists across restarts and broadcasts to all connected clients. Every UI element has `lx-*` anchor classes for granular styling. Reset to default with `\"reset theme\"`. |\n| **SurrealDB Memory (Layer 3)** | ✅ Complete | Embedded file-backed SurrealDB (`surrealkv://`). Persists: UI state (widgets), command history, shell sessions, named workspaces, organ registrations, scrape patterns, scraped data, automations, themes, active theme. All widget/shell data is workspace-scoped. Auto-restores on reconnect. No external server needed. |\n| **ZeroMQ Spine (Layer 2)** | ✅ Complete | PUSH/PULL on `:5557` + PUB on `:5556`. Channels: `lexicon/toggle` (show/hide), `lexicon/theme` (apply theme by name). External scripts PUSH commands → Brain dispatches → WebSocket broadcast. HTTP fallback at `POST /toggle`. |\n| **Dev tooling** | ✅ Complete | `dev.sh` — menu-driven launcher (build / preview / dev mode). Starts Brain + Shell + Spine + Tauri. One command for everything. `lexicon-toggle` for hotkey binding. |\n\n### Extensions\n\n| Extension | Command Examples | What it does |\n|-----------|-----------------|--------------|\n| **clock** | `clock`, `time`, `what time is it` | Live-updating `HH:MM:SS` with gradient text |\n| **timer** | `timer 5m`, `countdown 1h30m`, `set timer 30s` | Countdown with progress bar, pause/reset controls |\n| **date** | `date`, `what day is it`, `today` | Weekday, date, year, day-of-year, week number, year-progress bar |\n| **note** | `note buy groceries`, `remind me to call bob` | Sticky notes with click-to-edit |\n| **calculator** | `calc 2+2`, `= pi * 2`, `math sqrt(144)` | Safe math eval with trig, log, constants |\n| **sysmon** | `system`, `stats`, `cpu` | Live CPU/RAM/disk bars polling from `/proc` |\n| **weather** | `weather`, `forecast` | Weather widget (demo mode, ready for API) |\n| **help** | `help`, `commands`, `?` | Auto-generated guide from all extensions |\n| **clear** | `clear`, `dismiss all`, `close` | Wipe all widgets from canvas |\n| **organ** | `organs`, `scrape`, `organ manager` | Opens the Organ Manager widget |\n| **view** | `view github`, `dashboard`, `show data` | Data view — renders scraped organ data |\n| **theme** | `theme cyberpunk`, `themes`, `reset theme` | Apply, list, or reset visual themes |\n| **person** | `people`, `person Rishi`, `who is Mehta`, `contacts` | Person dashboard — resolved entity identity graph |\n| **automation** | `automations`, `automate`, `crawl`, `workflow` | Programmable browser automation — build, save, run crawl sequences |\n\n### 🔲 Not Yet Implemented\n\n| Component | Layer | Notes |\n|-----------|-------|-------|\n| **Scheduled Automations** | 1 | Cron-like scheduling — run automations on a timer automatically. |\n| **More Organs** | 0+ | Discord, Gmail, etc. — same Playwright tab + scrape pattern. |\n| **CLI event tool** | 4 | `lexicon push \"meeting in 5min\"` from terminal via ZeroMQ PUSH to Spine. |\n| **SysMon daemon** | 4 | Push system metrics on schedule via Spine. |\n\n---\n\n## Getting Started\n\n### Prerequisites\n\n- **Linux** (any desktop environment — GNOME, KDE, Hyprland, Sway, etc.)\n- [Rust](https://rustup.rs/) + Cargo\n- [Bun](https://bun.sh/)\n- [uv](https://docs.astral.sh/uv/) (Python package manager)\n- Python 3.13+\n\n### Setup\n\n```bash\ngit clone https://github.com/vardhin/lexicon.git\ncd lexicon\n\n# Backend — install Python deps\ncd lexicon-backend\nuv sync\ncd ..\n\n# Shell microservice\ncd lexicon-shell\nuv sync\ncd ..\n\n# Frontend — install JS deps\ncd lexicon-frontend\nbun install\ncd ..\n```\n\n### Build \u0026 Run\n\n```bash\n./dev.sh\n```\n\nThis will:\n1. Build the Svelte static site (`bun run build`)\n2. Build the Tauri release binary (`bun run tauri build`)\n3. Start the Brain (FastAPI :8000) + Shell microservice (:8765) + ZeroMQ Spine (:5557/:5556)\n4. Launch the Tauri client in the background (hidden until toggled)\n\n### Toggle the overlay\n\nBind `lexicon-toggle` to your preferred hotkey:\n\n| DE | How to bind |\n|----|-------------|\n| **GNOME** | Settings → Keyboard → Custom Shortcuts → `Super+`` ` → `/path/to/lexicon/lexicon-toggle` |\n| **KDE** | System Settings → Shortcuts → Custom Shortcuts → add `lexicon-toggle` |\n| **Hyprland** | `bind = $mainMod, grave, exec, /path/to/lexicon/lexicon-toggle` |\n| **Sway** | `bindsym $mod+grave exec /path/to/lexicon/lexicon-toggle` |\n\nOr toggle manually:\n\n```bash\n# Via ZeroMQ (instant, \u003c5ms round-trip)\n./lexicon-toggle\n\n# Via HTTP (works from anywhere)\ncurl -X POST localhost:8000/toggle\n```\n\nPress **Escape** (with empty input) to hide the overlay.\n\n\u003e **How it works:** `lexicon-toggle` PUSHes `\"lexicon/toggle\"` via ZeroMQ to the Spine (`:5557`). The Brain receives it, broadcasts `TOGGLE_VISIBILITY` over WebSocket to the Svelte frontend, which calls `invoke(\"toggle_window\")` — a Rust IPC command that does the actual `window.show()` / `window.hide()`. This bypasses Wayland permission issues. The entire toggle round-trip is \u003c5ms.\n\n---\n\n## Theming\n\nLSD ships with 4 built-in themes. Type `themes` to list them, `theme \u003cname\u003e` to apply:\n\n| Theme | Vibe |\n|-------|------|\n| `cyberpunk` | Neon green on deep black, scanline overlay, terminal hacker aesthetic |\n| `midnight` | Deep navy blue with soft purple accents, calm and focused |\n| `rose-pine` | Warm muted tones — salmon, gold, teal from the Rosé Pine palette |\n| `ember` | Amber and orange on dark charcoal, warm and cozy |\n\n```\ntheme cyberpunk     ← apply a theme\nthemes              ← list all themes\nreset theme         ← revert to default\n```\n\nThemes are stored in SurrealDB and persist across restarts. The active theme auto-restores on reconnect and broadcasts to all connected clients. You can also trigger themes externally via the Spine: push `\"lexicon/theme cyberpunk\"` to `:5557`.\n\nCustom themes: drop a `.css` file in `themes/` — it's auto-seeded on next boot. Target `lx-*` classes (`lx-widget`, `lx-bar`, `lx-input`, `lx-sidebar`, etc.) to style any element.\n\n---\n\n## Adding Extensions\n\nEach extension is a single Python file in `extensions/` with a standard interface:\n\n### 1. Create the backend logic\n\n```python\n# extensions/timer.py\nimport re, uuid\n\ndef match(text):\n    m = re.search(r\"timer\\s+(\\d+)\\s*(m|min|s|sec)?\", text)\n    if m:\n        amount = int(m.group(1))\n        unit = (m.group(2) or \"s\")[0]\n        return amount * (60 if unit == \"m\" else 1)\n    return None\n\ndef action(original_text, seconds):\n    return {\n        \"type\": \"RENDER_WIDGET\",\n        \"widget_id\": f\"timer-{uuid.uuid4().hex[:6]}\",\n        \"widget_type\": \"timer\",\n        \"x\": 100, \"y\": 100, \"w\": 300, \"h\": 180,\n        \"props\": {\"duration_seconds\": seconds},\n    }\n\nEXTENSION = {\n    \"name\": \"timer\",\n    \"match\": match,\n    \"action\": action,\n    \"help\": {\n        \"title\": \"Timer\",\n        \"icon\": \"⏱\",\n        \"description\": \"Set a countdown timer\",\n        \"examples\": [\"timer 5m\", \"countdown 30s\"],\n    },\n}\n```\n\n### 2. Create the frontend widget\n\n```svelte\n\u003c!-- lexicon-frontend/src/lib/widgets/TimerWidget.svelte --\u003e\n\u003cscript\u003e\n  export let props = {};\n  export let onDismiss = () =\u003e {};\n  // ... timer logic\n\u003c/script\u003e\n\u003cdiv class=\"timer-widget lx-timer\"\u003e\n  \u003c!-- ... timer UI with lx-* classes for theming --\u003e\n\u003c/div\u003e\n```\n\n### 3. Register it\n\n```javascript\n// lexicon-frontend/src/lib/widgets/index.js\nimport TimerWidget from './TimerWidget.svelte';\n\nconst registry = {\n  // ...\n  timer: TimerWidget,  // ← add here\n};\n```\n\nRestart the backend (it auto-reloads via uvicorn), rebuild the frontend (`./dev.sh`). Done.\n\nExtensions can also return custom action types (not just `RENDER_WIDGET`) — the Brain's WebSocket handler intercepts them. See `extensions/theme.py` for an example that returns `THEME_APPLY` / `THEME_RESET` / `THEME_LIST_REQUEST`.\n\n---\n\n## Widget Conventions\n\nEvery widget in LSD follows a strict set of conventions so that theming, layout, and composability work uniformly across the system.\n\n### Required exports\n\nEvery widget Svelte component must export exactly two props:\n\n```svelte\n\u003cscript\u003e\n  export let props = {};      // Data/config from the backend (RENDER_WIDGET.props)\n  export let onDismiss = () =\u003e {};  // Called when the user clicks the ✕ dismiss button\n\u003c/script\u003e\n```\n\n### Root element structure\n\nThe outermost `\u003cdiv\u003e` must:\n\n1. Have a **widget-specific CSS class**: e.g., `clock-widget`, `person-widget`\n2. Have a **`lx-*` theme anchor class**: e.g., `lx-clock`, `lx-person`, `lx-widget`\n3. Fill its container: `width: 100%; height: 100%;`\n4. Include a **dismiss button** with class `dismiss lx-dismiss`\n\n```svelte\n\u003cdiv class=\"my-widget lx-mywidget lx-widget\"\u003e\n  \u003cbutton class=\"dismiss lx-dismiss\" on:click={onDismiss}\u003e✕\u003c/button\u003e\n  \u003c!-- widget content --\u003e\n\u003c/div\u003e\n```\n\n### Theme anchor classes (`lx-*`)\n\nEvery significant element should have an `lx-*` class so themes can target it. The convention:\n\n| Scope | Class Pattern | Example |\n|-------|--------------|---------|\n| Widget root | `lx-{widget}` | `lx-clock`, `lx-person`, `lx-sysmon` |\n| Generic widget | `lx-widget` | All widgets |\n| Dismiss button | `lx-dismiss` | `✕` close button |\n| Label/header | `lx-label` | `\"CLOCK\"`, `\"SYSTEM\"` section labels |\n| Input fields | `lx-input` | Search bars, text inputs |\n| Sub-elements | `lx-{widget}-{part}` | `lx-person-header`, `lx-clock-time`, `lx-person-card` |\n\nThemes target these via CSS:\n```css\n/* themes/cyberpunk.css */\n.lx-person-card { border-color: rgba(0, 255, 65, 0.2); }\n.lx-person-name { color: #00ff41; }\n.lx-dismiss:hover { color: #ff0040; }\n```\n\n### Styling conventions\n\n```css\n/* Standard glass widget styling */\n.my-widget {\n  position: relative; width: 100%; height: 100%;\n  display: flex; flex-direction: column;\n  color: rgba(255,255,255,0.92);\n  font-family: 'Inter', 'Segoe UI', system-ui, sans-serif;\n  padding: 10px 14px;\n  box-sizing: border-box;\n  overflow: hidden;\n}\n\n/* Standard dismiss button */\n.dismiss {\n  position: absolute; top: 6px; right: 10px;\n  background: none; border: none;\n  color: rgba(255,255,255,0.3); font-size: 14px;\n  cursor: pointer; z-index: 10;\n  padding: 2px 6px; border-radius: 4px;\n}\n.dismiss:hover { color: #ff5f57; background: rgba(255,95,87,0.12); }\n```\n\n### Backend → Frontend contract\n\nThe backend `action()` returns a `RENDER_WIDGET` message:\n\n```python\n{\n    \"type\": \"RENDER_WIDGET\",\n    \"widget_id\": \"unique-id\",\n    \"widget_type\": \"mywidget\",          # Must match registry key in index.js\n    \"x\": 100, \"y\": 100,\n    \"w\": 400, \"h\": 300,\n    \"props\": { ... },                   # Passed to the Svelte component as `props`\n}\n```\n\n### Checklist for new widgets\n\n- [ ] Backend extension in `extensions/` with `match()`, `action()`, `EXTENSION` dict, and `help` entry\n- [ ] Frontend widget in `src/lib/widgets/` with `export let props` + `export let onDismiss`\n- [ ] Root element has widget-specific class + `lx-*` theme anchor(s)\n- [ ] Dismiss button with `lx-dismiss`\n- [ ] Registered in `src/lib/widgets/index.js`\n- [ ] All interactive elements have `lx-*` classes for theme injection\n- [ ] Widget fills container (`width: 100%; height: 100%`)\n- [ ] Scrollable content areas use `scrollbar-width: thin`\n\n---\n\n## Entity Resolution\n\nLSD includes a built-in **identity resolution engine** that automatically merges scraped data from different organs (WhatsApp, GitHub, etc.) into unified person nodes.\n\n### How it works\n\nWhen you scrape data from an organ (manually or via automation), the Brain's entity resolver automatically:\n\n1. **Extracts identity signals** from each scraped item — names, usernames, phone numbers, emails, avatar URLs (using spaCy NER for classification)\n2. **Compares signals** against all existing entity nodes using 5 independent voting strategies\n3. **Merges or creates** — if the consensus score exceeds the threshold, the item is merged into an existing entity; otherwise a new entity is created\n\n### The 5 voting strategies\n\n| Strategy | Weight | What it matches | Example |\n|----------|--------|-----------------|---------|\n| **Fingerprint** | 5.0 | Exact phone/email match (deterministic) | `+91-9876543210` = `+91 9876 543210` → auto-merge |\n| **Name Similarity** | 2.5 | Jaro-Winkler + Soundex + Double Metaphone | `\"Rishi Metha\"` ~ `\"Rishi Mehta\"` → 0.95 |\n| **Username Match** | 2.0 | Cross-platform handle correlation | `rishimehta04` ~ `rishi_mehta` → 0.90 |\n| **Token Overlap** | 1.5 | IDF-weighted Jaccard + substring containment | `\"rishimehta\"` contains `{\"rishi\", \"mehta\"}` |\n| **Contextual** | 4.5 | Same avatar URL, phone digit patterns | Shared avatar → 0.85 |\n\nThe **consensus engine** only averages strategies that are *applicable* — if neither side has a phone number, the fingerprint strategy is excluded rather than dragging the score to zero.\n\n### Natural language commands\n\n```\npeople              ← show all resolved person nodes\nperson Rishi        ← search for a specific person\nwho is Mehta        ← search by name\ncontacts            ← alias for people\n```\n\n---\n\n## Automations\n\nLSD's automation engine goes beyond static scraping — it lets you **dynamically crawl** through Playwright tabs by executing programmable action sequences.\n\n### What are automations?\n\nAn automation is a named sequence of **steps**. Each step is an action that runs against an organ's live Playwright page. Steps execute in order, and extracted data is automatically stored and resolved into entity nodes.\n\n### Available step types\n\n| Step | What it does | Key params |\n|------|-------------|------------|\n| **click** | Click an element | `selector`, `button`, `count`, `wait_after` |\n| **type** | Type text into an input | `selector`, `text`, `clear`, `press_enter`, `delay` |\n| **scroll** | Scroll the page or element | `direction` (down/up/bottom/top), `amount`, `selector` |\n| **wait** | Wait for a condition | `selector` + `state`, or `delay` (ms), or network idle |\n| **navigate** | Go to a URL | `url` (absolute or relative), `wait_until` |\n| **extract** | Scrape data from the page | `outer_html` (full structural) or `selector` + `attribute` |\n| **paginate** | Click through pages, extracting each | `next_selector`, `extract`, `max_pages`, `stop_if_empty` |\n| **eval_js** | Run arbitrary JavaScript | `js` (must return a value) |\n| **screenshot** | Capture the page | `full_page`, `selector`, `quality` |\n| **conditional** | Run sub-steps if selector exists | `selector`, `then`, `otherwise` |\n| **loop** | Repeat sub-steps N times | `count`, `steps`, `stop_selector`, `stop_if_no_change` |\n\n### Example: Crawl GitHub trending repos\n\n```json\n{\n  \"name\": \"github_trending\",\n  \"description\": \"Scroll through GitHub trending and extract all repos\",\n  \"steps\": [\n    { \"type\": \"navigate\", \"url\": \"https://github.com/trending\" },\n    { \"type\": \"wait\", \"delay\": 2000 },\n    { \"type\": \"scroll\", \"direction\": \"bottom\", \"wait_after\": 1500 },\n    { \"type\": \"extract\", \"selector\": \"article.Box-row h2 a\", \"attribute\": \"href\" }\n  ]\n}\n```\n\n### Example: Search and paginate\n\n```json\n{\n  \"name\": \"search_python_repos\",\n  \"steps\": [\n    { \"type\": \"navigate\", \"url\": \"https://github.com/search?q=python\u0026type=repositories\" },\n    { \"type\": \"wait\", \"selector\": \"[data-testid='results-list']\" },\n    { \"type\": \"paginate\",\n      \"next_selector\": \"a.next_page\",\n      \"extract\": { \"selector\": \"div.search-title a\", \"attribute\": \"textContent\" },\n      \"max_pages\": 3 }\n  ]\n}\n```\n\n### Example: Infinite scroll extraction\n\n```json\n{\n  \"name\": \"scroll_and_extract\",\n  \"steps\": [\n    { \"type\": \"loop\", \"count\": 5, \"stop_if_no_change\": true, \"steps\": [\n      { \"type\": \"scroll\", \"direction\": \"down\", \"amount\": 1200, \"wait_after\": 2000 },\n      { \"type\": \"extract\", \"selector\": \".feed-item h3\", \"attribute\": \"textContent\" }\n    ]}\n  ]\n}\n```\n\n### One-shot actions\n\nFor quick ad-hoc interactions without building a full automation:\n\n| Endpoint | Method | Description |\n|----------|--------|-------------|\n| `/organs/{id}/actions/click` | POST | Click an element |\n| `/organs/{id}/actions/type` | POST | Type into an input |\n| `/organs/{id}/actions/scroll` | POST | Scroll the page |\n| `/organs/{id}/actions/navigate` | POST | Navigate to a URL |\n| `/organs/{id}/actions/screenshot` | POST | Take a screenshot |\n| `/organs/{id}/actions/eval` | POST | Evaluate JavaScript |\n| `/organs/{id}/actions/extract` | POST | Extract data with a pattern |\n| `/organs/{id}/actions/paginate` | POST | Paginate + extract |\n\n### Natural language commands\n\n```\nautomations         ← open the automation builder widget\nautomate            ← same\ncrawl               ← same\nworkflow            ← same\n```\n\n---\n\n## Project Structure\n\n```\nlexicon/\n├── dev.sh                          # Menu-driven launcher (build / preview / dev)\n├── lexicon-toggle                  # Toggle script — bind to your DE hotkey\n├── lexicon-toggle.sh               # Alternative toggle script\n│\n├── extensions/                     # Backend extensions (Python, auto-loaded)\n│   ├── automation.py               #   Automation manager widget trigger\n│   ├── calculator.py               #   Inline math evaluator\n│   ├── clear.py                    #   Clear all widgets\n│   ├── clock.py                    #   Clock widget\n│   ├── date.py                     #   Date display widget\n│   ├── help.py                     #   Help guide (auto-collects from all extensions)\n│   ├── note.py                     #   Sticky notes\n│   ├── organ.py                    #   Organ Manager widget trigger\n│   ├── person.py                   #   Person dashboard — entity identity graph\n│   ├── sysmon.py                   #   System monitor widget\n│   ├── theme.py                    #   Theme apply / list / reset\n│   ├── timer.py                    #   Countdown timer widget\n│   ├── view.py                     #   Data view / dashboard widget\n│   └── weather.py                  #   Weather widget (demo)\n│\n├── themes/                         # Built-in themes (auto-seeded to SurrealDB)\n│   ├── cyberpunk.css\n│   ├── ember.css\n│   ├── midnight.css\n│   └── rose-pine.css\n│\n├── lexicon-backend/                # Layer 1: The Brain\n│   ├── pyproject.toml\n│   ├── run.sh\n│   └── src/\n│       ├── main.py                 #   FastAPI + WS + organ + automation + entity endpoints\n│       ├── engine.py               #   Grammar engine (auto-loads extensions/)\n│       ├── automation.py           #   Automation engine — programmable browser actions\n│       ├── entity_resolver.py      #   Multi-strategy identity resolution (spaCy NER)\n│       ├── memory.py               #   SurrealDB embedded memory (Layer 3)\n│       ├── spine.py                #   ZeroMQ event bus (Layer 2)\n│       ├── shell.py                #   Shell session manager\n│       ├── organ_manager.py        #   Playwright ghost browser + deep structural scraping\n│       └── connection_manager.py   #   WebSocket connection tracking + broadcast\n│\n├── lexicon-shell/                  # Layer 1+: Shell Microservice\n│   ├── pyproject.toml\n│   ├── run.sh\n│   └── shell_server.py             #   PTY server on :8765\n│\n├── lexicon-frontend/               # Layer 0: The Body\n│   ├── package.json\n│   ├── src/\n│   │   ├── app.html\n│   │   ├── routes/+page.svelte\n│   │   └── lib/\n│   │       ├── ws.js               #   WebSocket client (auto-reconnect)\n│   │       └── widgets/\n│   │           ├── index.js            # Widget registry (13 widgets)\n│   │           ├── AutomationWidget.svelte\n│   │           ├── ClockWidget.svelte\n│   │           ├── TimerWidget.svelte\n│   │           ├── DateWidget.svelte\n│   │           ├── NoteWidget.svelte\n│   │           ├── CalculatorWidget.svelte\n│   │           ├── SysMonWidget.svelte\n│   │           ├── WeatherWidget.svelte\n│   │           ├── HelpWidget.svelte\n│   │           ├── TerminalWidget.svelte\n│   │           ├── OrganManagerWidget.svelte\n│   │           ├── DataViewWidget.svelte\n│   │           └── PersonWidget.svelte\n│   └── src-tauri/\n│       ├── tauri.conf.json\n│       └── src/\n│           ├── main.rs\n│           └── lib.rs\n│\n├── infra/\n│   └── data/                       #   SurrealDB file store (auto-created)\n│\n└── architecture/                   # Architecture diagram (Mermaid)\n```\n\n---\n\n## Roadmap\n\n- [x] **SurrealDB Memory** — persist UI state, command history, auto-restore on launch\n- [x] **Extensions** — clock, timer, date, weather, notes, calculator, system monitor, help, clear, organ manager, data view, theme, person, automation\n- [x] **Widget dragging + resizing** — pointer-based repositioning, corner resize, persisted\n- [x] **Paged workspace** — scrollable multi-page canvas with sidebar navigation\n- [x] **Multi-session shell** — real PTY sessions via Shell microservice, xterm.js rendering, multiple terminals\n- [x] **ZeroMQ Spine** — PUSH/PULL + PUB event bus, `lexicon-toggle`, `lexicon/theme` channels\n- [x] **Named workspaces** — create, switch, delete workspaces with independent state\n- [x] **Generic organ system** — any URL as a Playwright ghost browser tab, deep structural HTML scraping, pattern matching, field extraction\n- [x] **Theming** — 4 built-in themes, SurrealDB persistence, `lx-*` CSS anchors, Spine channel, natural language control\n- [x] **Entity resolver** — multi-strategy consensus identity resolution (spaCy NER), auto-resolves scraped data into person nodes, PersonWidget dashboard\n- [x] **Automation engine** — programmable browser automation: click, scroll, type, wait, navigate, extract, paginate, eval, conditional, loop. Saved workflows per organ. One-shot actions. Auto-entity-resolution on extracted data\n- [ ] **Scheduled automations** — cron-like scheduling for automations to run on a timer\n- [ ] **More Organs** — Discord, Gmail, etc.\n- [ ] **CLI event tool** — `lexicon push \"reminder text\"` from terminal via Spine\n- [ ] **SysMon daemon** — push system metrics on schedule via Spine\n\n---\n\n## License\n\nMIT\n\n---\n\n\u003cp align=\"center\"\u003e\n  Built by \u003ca href=\"https://github.com/vardhin\"\u003e@vardhin\u003c/a\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvardhin%2Flexicon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvardhin%2Flexicon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvardhin%2Flexicon/lists"}