Skip to content

Desktop Mode

Keyur Aghao edited this page Sep 27, 2026 · 1 revision

Desktop Mode

aisrf desktop runs the whole gateway on your own machine for one reviewer: it binds to the loopback interface, keeps its state in a per-user application directory, generates its secrets on first run and opens the dashboard in a native window or your browser. It is the default suggestion of the install scripts and the quickest way to try AISRF. Implementation: aisrf/desktop.py; CLI wiring: the desktop command in aisrf/cli.py and the aisrf-desktop console script in pyproject.toml.

What aisrf desktop does

  1. configure_environment(force=True) resolves the app directory, creates it, seeds aisrf.env (see below) and exports AISRF_DATA_DIR, AISRF_LOG_DIR, AISRF_DATABASE_URL and AISRF_HOME into the process environment with os.environ.setdefault, so anything you set explicitly still wins.
  2. DesktopServer builds a uvicorn.Config for aisrf.main:create_app (factory mode, one worker, host 127.0.0.1, a free port from free_port() unless --port is given, log level from AISRF_LOG_LEVEL) and runs it in a daemon thread named aisrf-uvicorn.
  3. wait_for_health() polls http://127.0.0.1:<port>/healthz every 250 ms for up to 60 s; if the thread dies or the timeout passes the server is stopped and the command exits with code 1 and the message "the gateway did not become healthy; check the log directory for details".
  4. It prints a banner:
AISRF 1.0.0 desktop mode
  app directory : /home/you/.local/share/aisrf
  settings file : /home/you/.local/share/aisrf/aisrf.env
  starting the gateway ...
  dashboard     : http://127.0.0.1:43211
  sign in as    : admin / admin (change it under Settings)
  API token     : AISRF_ADMIN_API_TOKEN in aisrf.env (for the aisrf CLI and MCP)
  press Ctrl+C to stop
  1. It installs signal handlers (SIGINT, SIGTERM and SIGBREAK on Windows raise KeyboardInterrupt), then tries a pywebview window; when that is unavailable it opens the system browser with webbrowser.open() and waits while the server thread is alive.
  2. On window close, Ctrl+C or a signal, DesktopServer.stop() sets should_exit, joins the thread for 15 s, then forces exit and waits 5 more seconds; the command prints "gateway stopped". If the gateway dies on its own the exit code is 1 with "the gateway stopped unexpectedly".

Everything else is the normal gateway: point local agents at http://127.0.0.1:<port>/v1 with an agent key, use the same REST API, MCP endpoint at /mcp, and CLI (aisrf tickets list --url http://127.0.0.1:<port> --token <AISRF_ADMIN_API_TOKEN>).

Application directory per OS

app_dir() follows the platformdirs conventions without the dependency. AISRF_HOME always wins when set.

OS Directory
Linux and other POSIX $XDG_DATA_HOME/aisrf, default ~/.local/share/aisrf
macOS ~/Library/Application Support/AISRF
Windows %LOCALAPPDATA%\AISRF (fallback ~\AppData\Local\AISRF)

Layout inside the directory:

aisrf.env            generated settings (0600 on POSIX)
data/aisrf.db        SQLite database, WAL mode (plus aisrf.db-wal and aisrf.db-shm while running)
data/codereview/     work directories of code review runs
logs/aisrf.jsonl     service log, rotating 50 MB x 10
logs/agents/<id>.jsonl   one rotating file per agent, 20 MB x 5

The same directory logic applies to the PyInstaller binaries for every command (serve, init-db, agent create, ...): packaging/pyinstaller/launcher.py calls configure_environment() before handing over to the CLI, and it activates whenever sys.frozen is set or AISRF_HOME is present. A pip install without AISRF_HOME keeps writing to ./data and ./logs except in desktop mode, where the app directory is forced.

The generated aisrf.env

ensure_env_file() creates <app dir>/aisrf.env when it is missing or when either generated key is empty:

# Generated by AISRF on first run. Keep this file private: it holds the key that encrypts
# stored upstream credentials and the admin API token used by the CLI and the MCP server.
# Delete a line to have it regenerated; any AISRF_* setting can be added here.
AISRF_SECRET_KEY=<secrets.token_urlsafe(48)>
AISRF_ADMIN_API_TOKEN=<secrets.token_urlsafe(48)>

Rules: existing values are preserved (repeated starts return the same secrets), only missing keys are regenerated, the file is chmod 0600 on POSIX, comments and blank lines are ignored, surrounding quotes are stripped. Any AISRF_* setting can be added, for example AISRF_ADMIN_PASSWORD=..., AISRF_PORT=8080 (for serve), AISRF_APPROVAL_TIMEOUT_SECONDS=600 or AISRF_DATABASE_URL=postgresql+asyncpg://.... Values from the file are applied with setdefault, so a real environment variable overrides the file. Deleting the AISRF_SECRET_KEY line regenerates it on the next start, which makes previously stored upstream credentials unreadable; treat that as a reset.

pywebview window versus browser fallback

_open_window() imports webview (pywebview). When the import succeeds it creates a 1360 x 880 window (minimum 900 x 600) titled <AISRF_APP_NAME> <version> and blocks until the window is closed; closing it stops the gateway. Missing GUI backends are common, so pywebview logging is set to CRITICAL and any exception (no GTK, Qt or WebView2 runtime) prints native window unavailable (...), opening the browser instead and falls back.

  • pip install: uv pip install --python .venv/bin/python "aisrf[desktop]" adds pywebview>=5. Linux additionally needs GTK (python3-gi, gir1.2-webkit2-4.1) or Qt (PyQt5 + PyQtWebEngine) bindings; macOS uses WebKit and Windows uses WebView2 (present on Windows 11 and updated Windows 10).
  • Binaries: pywebview is excluded from the bundle (EXCLUDES in packaging/pyinstaller/manifest.py), so the binaries always open the system browser.
  • Browser fallback: webbrowser.open(url); the process then idles until Ctrl+C. With --no-browser nothing is opened and only the URL is printed.

Flags

aisrf desktop [--port N] [--no-window] [--no-browser]
aisrf-desktop [--port N] [--port=N] [--no-window] [--no-browser] [-h | --help]
Flag Effect
--port N bind 127.0.0.1:N instead of a random free port; use it when agents need a stable URL
--no-window skip pywebview and open the system browser
--no-browser open nothing, just print the URL (also implies no window when combined with --no-window; on its own the window is still tried)

The aisrf-desktop console script parses the same flags by hand and exits with status 2 on an unknown argument. Environment variables apply as usual: AISRF_HOME relocates the directory, AISRF_LOG_LEVEL=DEBUG makes uvicorn verbose, AISRF_ADMIN_PASSWORD sets the admin password on a fresh database.

Stopping

Close the window, press Ctrl+C in the terminal, or send SIGTERM to the process. Shutdown runs the FastAPI lifespan teardown: sweeper cancelled, notifier stopped, campaign, scan and code review runners shut down, HTTP client and database engine disposed. Pending tickets stay PENDING in the database and expire later through the sweeper when the gateway runs again; synchronous callers get a connection error.

Upgrading

Desktop mode has no state of its own beyond the app directory, so upgrading is upgrading the binary or the package:

  • Binary: re-run scripts/install.sh or scripts/install.ps1 while aisrf is not running (the Windows installer refuses to overwrite a running aisrf.exe). The app directory, database and aisrf.env are untouched.
  • pip: pipx upgrade aisrf or uv pip install --python .venv/bin/python -U "git+https://github.com/keyuraghao/aisrf.git".

The schema is extended with create_all on the next start. Back up data/aisrf.db and aisrf.env before a major version.

Resetting

Goal Action
Forget the admin password stop, run aisrf create-reviewer newadmin --role admin with AISRF_HOME pointing at the app directory (the binary sets it automatically), sign in with the new account and change the old one under Settings > Reviewers
New admin API token delete the AISRF_ADMIN_API_TOKEN line in aisrf.env, start again; update every MCP client config
Wipe tickets and agents but keep the secrets stop, delete data/aisrf.db (and -wal, -shm), start again; agents and their upstream keys must be recreated
Full factory reset stop and delete the whole app directory (rm -rf ~/.local/share/aisrf, rm -rf ~/Library/Application\ Support/AISRF, or Remove-Item -Recurse $env:LOCALAPPDATA\AISRF)
Move the state elsewhere copy the directory and set AISRF_HOME=/new/path before starting

A full reset also deletes the audit chain; export it first with aisrf report audit --format json --out audit.json if you need the history.

Clone this wiki locally