diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5d52a3f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,411 @@ +# 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)