Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,60 @@ hermes gateway # start the scheduler (keep running)
hermes cron list # verify "Sorelax 6h Context Refresh"
```

## GUI (Desktop App)

Sorelax ships a **PyQt6 desktop interface** — a dark, Tokyo Night themed dashboard with
live log tailing, interactive Q&A, a context explorer, and a credential manager.

### Install

PyQt6 is an optional dependency (not pulled in by the base `pip install -e .`):

```bash
pip install PyQt6
pip install -e . # re-installs to register the sorelax-gui script
```

### Launch

```bash
sorelax-gui
```

Or directly:

```bash
python -m gui.main
```

### First-run wizard

If no `.env` is found the app opens the **Setup Wizard** automatically:

| Step | What happens |
|------|-------------|
| **1 — Dependency Check** | Verifies `curl`, `pip`, and `coral` are on PATH. Shows install command if Coral is missing. |
| **2 — Credentials** | Collects all 7 tokens (GitHub, Linear, Slack, Notion, Gemini, …) with password echo. |
| **3 — Install Progress** | Runs the full install pipeline in a background thread with colour-coded live output. |
| **4 — Done** | Displays copyable `hermes model / gateway` commands and opens the main dashboard. |

### Dashboard panels

| Panel | Description |
|-------|-------------|
| **Dashboard** | Coloured source health dots, last-sync time, countdown timer, Refresh Now + Start/Stop scheduler buttons, live console |
| **Ask Sorelax** | Natural-language Q&A streamed live from `sorelax ask`, with session history sidebar |
| **Context Explorer** | Scrollable cards built from `~/.sorelax/project_context.json`, grouped by key |
| **Live Logs** | Auto-scrolling tail of `~/.sorelax/project_log.jsonl` with Pause/Resume |
| **Settings** | 7 credential fields (password mode, Show/Hide toggle), Save applies Coral sources off-thread |

> **Screenshot**
>
> *(Add a screenshot here once the app is running — e.g. `![Dashboard](docs/screenshot_dashboard.png)`)*

## Usage


### Sorelax CLI

```bash
Expand Down
97 changes: 97 additions & 0 deletions cli/website.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Launch the Sorelax dashboard website (Vite dev server)."""

from __future__ import annotations

import shutil
import signal
import subprocess
import sys
import threading
import time
import webbrowser
from pathlib import Path

import typer


def _repo_root() -> Path:
return Path(__file__).resolve().parent.parent


def _website_dir() -> Path:
website = _repo_root() / "website"
if not website.is_dir():
typer.echo(
f"Cannot find website/ at {website}. "
"Run sorelax-website from an installed Sorelax checkout.",
err=True,
)
raise typer.Exit(1)
return website


def _ensure_deps(website: Path) -> None:
if (website / "node_modules").is_dir():
return
if not shutil.which("npm"):
typer.echo("npm is required. Install Node.js and try again.", err=True)
raise typer.Exit(1)
typer.echo("Installing frontend dependencies (first run)...")
subprocess.run(["npm", "install"], cwd=website, check=True)
Comment on lines +39 to +40


def _launch(
port: int = typer.Option(5173, "--port", "-p", help="Vite dev server port"),
host: str = typer.Option("127.0.0.1", "--host", help="Bind address"),
no_open: bool = typer.Option(False, "--no-open", help="Do not open the browser"),
) -> None:
"""Start the dashboard and open it in your default browser."""
website = _website_dir()
_ensure_deps(website)

if not shutil.which("npm"):
typer.echo("npm is required. Install Node.js and try again.", err=True)
raise typer.Exit(1)

url = f"http://{host}:{port}/dashboard"
typer.echo(f"\n Sorelax Website → {url}\n")
typer.echo(" Press Ctrl+C to stop.\n")

if not no_open:

def _open_browser() -> None:
time.sleep(1.8)
webbrowser.open(url)

threading.Thread(target=_open_browser, daemon=True).start()

proc = subprocess.Popen(
[
"npm",
"run",
"dev",
"--",
"--host",
host,
"--port",
str(port),
"--strictPort",
],
cwd=website,
)

def _handle_signal(_signum: int, _frame: object) -> None:
proc.send_signal(signal.SIGINT)

signal.signal(signal.SIGINT, _handle_signal)
signal.signal(signal.SIGTERM, _handle_signal)

sys.exit(proc.wait())


def main() -> None:
typer.run(_launch)


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions gui/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Sorelax GUI package — PyQt6 desktop interface."""
Loading