# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Build & Development Commands ```bash just build # Debug build (all workspace members) just build-ui # UI binary only just build-daemon # Core daemon only just release # Release build (LTO, stripped) just release-daemon # Release build for daemon only just check # cargo check + clippy just test # Run tests just fmt # Format code just run [ARGS] # Run UI with optional args (e.g., just run --mode app) just run-daemon # Run core daemon just install-local # Install core + daemon + runtimes + systemd units # Dev build with verbose logging cargo run -p owlry --features dev-logging # Build core without embedded Lua (smaller binary, uses external owlry-lua) cargo build -p owlry --release --no-default-features ``` ## Usage Examples ### Basic Invocation The UI client connects to the `owlry-core` daemon via Unix socket IPC. Start the daemon first: ```bash # Start daemon (systemd recommended) systemctl --user enable --now owlry-core.service # Or run directly owlry-core # Then launch UI owlry # Launch with all providers owlry -m app # Applications only owlry -m cmd # PATH commands only owlry --profile dev # Use a named config profile owlry -m calc # Calculator plugin only (if installed) ``` ### dmenu Mode dmenu mode runs locally without the daemon. Use `-m dmenu` with piped input for interactive selection. The selected item is printed to stdout (not executed), so pipe the output to execute it: ```bash # Screenshot menu (execute selected command) printf '%s\n' \ "grimblast --notify copy screen" \ "grimblast --notify copy area" \ "grimblast --notify edit screen" \ | owlry -m dmenu -p "Screenshot" \ | sh # Git branch checkout git branch | owlry -m dmenu -p "checkout" | xargs git checkout # Kill a process ps -eo comm | sort -u | owlry -m dmenu -p "kill" | xargs pkill # Select and open a project find ~/projects -maxdepth 1 -type d | owlry -m dmenu | xargs code ``` ### CLI Flags | Flag | Description | |------|-------------| | `-m`, `--mode MODE` | Start in single-provider mode (app, cmd, dmenu, calc, etc.) | | `--profile NAME` | Use a named profile from config (defines which modes to enable) | | `-p`, `--prompt TEXT` | Custom prompt text for the search input (dmenu mode) | ### Available Modes | Mode | Description | |------|-------------| | `app` | Desktop applications | | `cmd` | PATH commands | | `dmenu` | Pipe-based selection (requires stdin, runs locally) | | `calc` | Calculator (plugin) | | `clip` | Clipboard history (plugin) | | `emoji` | Emoji picker (plugin) | | `ssh` | SSH hosts (plugin) | | `sys` | System actions (plugin) | | `bm` | Bookmarks (plugin) | | `file` | File search (plugin) | | `web` | Web search (plugin) | | `uuctl` | systemd user units (plugin) | ### Search Prefixes Type these in the search box to filter by provider: | Prefix | Provider | Example | |--------|----------|---------| | `:app` | Applications | `:app firefox` | | `:cmd` | PATH commands | `:cmd git` | | `:sys` | System actions | `:sys shutdown` | | `:ssh` | SSH hosts | `:ssh server` | | `:clip` | Clipboard | `:clip password` | | `:bm` | Bookmarks | `:bm github` | | `:emoji` | Emoji | `:emoji heart` | | `:calc` | Calculator | `:calc sqrt(16)` | | `:web` | Web search | `:web rust docs` | | `:file` | Files | `:file config` | | `:uuctl` | systemd | `:uuctl docker` | | `:tag:X` | Filter by tag | `:tag:development` | ### Trigger Prefixes | Trigger | Provider | Example | |---------|----------|---------| | `=` | Calculator | `= 5+3` | | `?` | Web search | `? rust programming` | | `/` | File search | `/ .bashrc` | ### Keyboard Shortcuts | Key | Action | |-----|--------| | `Enter` | Launch selected item | | `Escape` | Close launcher / exit submenu | | `Up` / `Down` | Navigate results | | `Tab` | Cycle filter tabs | | `Shift+Tab` | Cycle tabs (reverse) | | `Ctrl+1..9` | Toggle tab by position | ### Plugin CLI ```bash owlry plugin list # List installed owlry plugin list --available # Show registry owlry plugin search "query" # Search registry owlry plugin install # Install from registry owlry plugin install ./path # Install from local path owlry plugin remove # Uninstall owlry plugin enable/disable # Toggle owlry plugin create # Create Lua plugin template owlry plugin create -r rune # Create Rune plugin template owlry plugin validate ./path # Validate plugin structure owlry plugin run [args] # Run plugin CLI command owlry plugin commands # List plugin commands owlry plugin runtimes # Show available runtimes ``` ## Release Workflow Always use `just` for releases - do NOT manually edit Cargo.toml for version bumps: ```bash # Bump a single crate just bump-crate owlry-core 0.5.1 # Bump all crates to same version just bump-all 0.5.1 # Bump core UI only just bump 0.5.1 # Create and push release tag git push && just tag # AUR package management just aur-update # Update core UI PKGBUILD just aur-update-pkg NAME # Update specific package (owlry-core, owlry-lua, etc.) just aur-update-all # Update all AUR packages just aur-publish # Publish core UI to AUR just aur-publish-all # Publish all AUR packages # Version inspection just show-versions # List all crate versions just aur-status # Show AUR package versions and git status ``` ## AUR Packaging The `aur/` directory contains PKGBUILDs for core packages: | Category | Packages | |----------|----------| | Core UI | `owlry` | | Core Daemon | `owlry-core` | | Runtimes | `owlry-lua`, `owlry-rune` | | Meta-bundles | `owlry-meta-essentials`, `owlry-meta-widgets`, `owlry-meta-tools`, `owlry-meta-full` | Plugin AUR packages are in the separate `owlry-plugins` repo at `somegit.dev/Owlibou/owlry-plugins`. ## Architecture ### Client/Daemon Split Owlry uses a client/daemon architecture: - **`owlry`** (client): GTK4 UI that connects to the daemon via Unix socket IPC. Handles rendering, user input, and launching applications. In dmenu mode, runs a local `ProviderManager` directly (no daemon needed). - **`owlry-core`** (daemon): Headless background service that loads plugins, manages providers, handles fuzzy matching, frecency scoring, and serves queries over IPC. Runs as a systemd user service. ### Workspace Structure ``` owlry/ ├── Cargo.toml # Workspace root ├── systemd/ # systemd user service/socket files │ ├── owlry-core.service │ └── owlry-core.socket ├── crates/ │ ├── owlry/ # UI client binary (GTK4 + Layer Shell) │ │ └── src/ │ │ ├── main.rs # Entry point │ │ ├── app.rs # GTK Application setup, CSS loading │ │ ├── cli.rs # Clap CLI argument parsing │ │ ├── client.rs # CoreClient - IPC client to daemon │ │ ├── backend.rs # SearchBackend - abstraction over IPC/local │ │ ├── theme.rs # Theme loading │ │ ├── plugin_commands.rs # Plugin CLI subcommand handlers │ │ ├── providers/ # dmenu provider (local-only) │ │ └── ui/ # GTK widgets (MainWindow, ResultRow, submenu) │ ├── owlry-core/ # Daemon library + binary │ │ └── src/ │ │ ├── main.rs # Daemon entry point │ │ ├── lib.rs # Public API (re-exports modules) │ │ ├── server.rs # Unix socket IPC server │ │ ├── ipc.rs # Request/Response message types │ │ ├── filter.rs # ProviderFilter - mode/prefix filtering │ │ ├── paths.rs # XDG path utilities, socket path │ │ ├── notify.rs # Desktop notifications │ │ ├── config/ # Config loading (config.toml) │ │ ├── data/ # FrecencyStore │ │ ├── providers/ # Application, Command, native/lua provider hosts │ │ └── plugins/ # Plugin loading, manifests, registry, runtimes │ ├── owlry-plugin-api/ # ABI-stable plugin interface │ ├── owlry-lua/ # Lua script runtime (cdylib) │ └── owlry-rune/ # Rune script runtime (cdylib) ``` ### IPC Protocol Communication uses newline-delimited JSON over a Unix domain socket at `$XDG_RUNTIME_DIR/owlry/owlry.sock`. **Request types** (`owlry_core::ipc::Request`): | Type | Purpose | |------|---------| | `Query` | Search with text and optional mode filters | | `Launch` | Record a launch event for frecency | | `Providers` | List available providers | | `Refresh` | Refresh a specific provider | | `Toggle` | Toggle visibility (client-side concern, daemon acks) | | `Submenu` | Query submenu actions for a plugin item | | `PluginAction` | Execute a plugin action command | **Response types** (`owlry_core::ipc::Response`): | Type | Purpose | |------|---------| | `Results` | Search results with `Vec` | | `Providers` | Provider list with `Vec` | | `SubmenuItems` | Submenu actions for a plugin | | `Ack` | Success acknowledgement | | `Error` | Error with message | ### Core Data Flow ``` [owlry UI] [owlry-core daemon] main.rs → CliArgs → OwlryApp main.rs → Server::bind() ↓ ↓ SearchBackend UnixListener accept loop ↓ ↓ ┌──────┴──────┐ handle_request() ↓ ↓ ↓ Daemon Local (dmenu) ┌───────────┴───────────┐ ↓ ↓ ↓ CoreClient ──── IPC ────→ ProviderManager ProviderFilter ↓ ↓ [Provider impls] parse_query() ↓ LaunchItem[] ↓ FrecencyStore (boost) ↓ Response::Results ──── IPC ────→ UI rendering ``` ### Provider System **Core providers** (in `owlry-core`): - **Application**: Desktop applications from XDG directories - **Command**: Shell commands from PATH **dmenu provider** (in `owlry` client, local only): - **Dmenu**: Pipe-based input (dmenu compatibility) All other providers are native plugins in the separate `owlry-plugins` repo (`somegit.dev/Owlibou/owlry-plugins`). `ProviderManager` (in `owlry-core`) orchestrates providers and handles: - Fuzzy matching via `SkimMatcherV2` - Frecency score boosting - Native plugin loading from `/usr/lib/owlry/plugins/` **Submenu System**: Plugins can return items with `SUBMENU:plugin_id:data` commands. When selected, the plugin is queried with `?SUBMENU:data` to get action items (e.g., systemd service actions). ### Plugin API Native plugins use the ABI-stable interface in `owlry-plugin-api`: ```rust #[repr(C)] pub struct PluginVTable { pub info: extern "C" fn() -> PluginInfo, pub providers: extern "C" fn() -> RVec, pub provider_init: extern "C" fn(id: RStr) -> ProviderHandle, pub provider_refresh: extern "C" fn(ProviderHandle) -> RVec, pub provider_query: extern "C" fn(ProviderHandle, RStr) -> RVec, pub provider_drop: extern "C" fn(ProviderHandle), } // Each plugin exports: #[no_mangle] pub extern "C" fn owlry_plugin_vtable() -> &'static PluginVTable ``` Plugins are compiled as `.so` (cdylib) and loaded by the daemon at startup. **Plugin locations** (when deployed): - `/usr/lib/owlry/plugins/*.so` - Native plugins - `/usr/lib/owlry/runtimes/*.so` - Script runtimes (liblua.so, librune.so) - `~/.config/owlry/plugins/` - User plugins (Lua/Rune) ### Filter & Prefix System `ProviderFilter` (`owlry-core/src/filter.rs`) handles: - CLI mode selection (`--mode app`) - Profile-based mode selection (`--profile dev`) - Provider toggling (Ctrl+1/2/3) - Prefix parsing (`:app`, `:cmd`, `:sys`, etc.) Query parsing extracts prefix and forwards clean query to providers. ### SearchBackend `SearchBackend` (`owlry/src/backend.rs`) abstracts over two modes: - **`Daemon`**: Wraps `CoreClient`, sends queries over IPC to `owlry-core` - **`Local`**: Wraps `ProviderManager` directly (used for dmenu mode only) ### UI Layer - `MainWindow` (`src/ui/main_window.rs`): GTK4 window with Layer Shell overlay - `ResultRow` (`src/ui/result_row.rs`): Individual result rendering - `submenu` (`src/ui/submenu.rs`): Universal submenu parsing utilities (plugins provide actions) ### Configuration `Config` (`owlry-core/src/config/mod.rs`) loads from `~/.config/owlry/config.toml`: - Auto-detects terminal (`$TERMINAL` -> `xdg-terminal-exec` -> common terminals) - Optional `use_uwsm = true` for systemd session integration (launches apps via `uwsm app --`) - Profiles: Define named mode sets under `[profiles.]` with `modes = ["app", "cmd", ...]` ### Theming CSS loading priority (`owlry/src/app.rs`): 1. Base structural CSS (`resources/base.css`) 2. Theme CSS (built-in "owl" or custom `~/.config/owlry/themes/{name}.css`) 3. User overrides (`~/.config/owlry/style.css`) 4. Config variable injection ### Systemd Integration Service files in `systemd/`: - `owlry-core.service`: Runs daemon as `Type=simple`, restarts on failure - `owlry-core.socket`: Socket activation at `%t/owlry/owlry.sock` Start with: `systemctl --user enable --now owlry-core.service` ## Plugins Plugins live in a separate repository: `somegit.dev/Owlibou/owlry-plugins` 13 native plugin crates, all compiled as cdylib (.so): | Category | Plugins | Behavior | |----------|---------|----------| | Static | bookmarks, clipboard, emoji, scripts, ssh, system, systemd | Loaded at startup, refresh() populates items | | Dynamic | calculator, websearch, filesearch | Queried per-keystroke via query() | | Widget | weather, media, pomodoro | Displayed at top of results | ## Key Patterns - **Rc>** used throughout for GTK signal handlers needing mutable state - **Feature flag `dev-logging`**: Wraps debug!() calls in `#[cfg(feature = "dev-logging")]` - **Feature flag `lua`**: Enables built-in Lua runtime (off by default); enable to embed Lua in core binary - **dmenu mode**: Runs locally without daemon. Use `-m dmenu` with piped stdin - **Frecency**: Time-decayed frequency scoring stored in `~/.local/share/owlry/frecency.json` - **ABI stability**: Plugin interface uses `abi_stable` crate for safe Rust dynamic linking - **Plugin API v3**: Adds `position` (Normal/Widget) and `priority` fields to ProviderInfo - **ProviderType simplification**: Core uses only `Application`, `Command`, `Dmenu`, `Plugin(String)` - all plugin-specific types removed from core ## Dependencies (Rust 1.90+, GTK 4.12+) External tool dependencies (for plugins): - Clipboard plugin: `cliphist`, `wl-clipboard` - File search plugin: `fd` or `mlocate` - Emoji plugin: `wl-clipboard`, `noto-fonts-emoji` - Systemd plugin: `systemd` (user services) - Bookmarks plugin: Firefox support uses `rusqlite` with bundled SQLite (no system dependency)