{ config, lib, pkgs, ... }: with lib; let cfg = config.cli.rofi-project-opener; # Project directory submodule type projectDirType = types.submodule { options = { path = mkOption { type = types.str; description = "Base directory path to scan for project subdirectories."; example = "~/dev"; }; args = mkOption { type = types.str; default = ""; description = "Additional arguments to pass to opencode when launching projects from this directory."; example = "--agent Planner-Sisyphus"; }; }; }; # Convert projectDirs attrset to JSON for config file projectDirsJson = builtins.toJSON ( mapAttrs (name: value: { path = value.path; args = value.args; }) cfg.projectDirs ); in { options.cli.rofi-project-opener = { enable = mkEnableOption "Rofi-based project directory launcher"; projectDirs = mkOption { type = types.attrsOf projectDirType; default = { dev = {path = "~/dev";}; projects = {path = "~/projects";}; }; description = '' Attribute set of base directories to scan for project subdirectories. Each directory will be scanned for immediate subdirectories (non-hidden). Projects are displayed as "base_dir/project_name" in rofi. Each entry can specify: - path: Base directory path (supports ~ for home directory) - args: Optional arguments to pass to opencode for projects in this directory ''; example = literalExpression '' { nixpkgs = { path = "~/p/NIX/nixpkgs"; args = "--agent Planner-Sisyphus"; }; dev = { path = "~/dev"; }; work = { path = "~/work"; args = "--agent work-agent"; }; } ''; }; terminal = mkOption { type = types.either types.str types.package; default = "kitty"; description = "Terminal emulator to use for launching opencode. Can be a string or package."; example = literalExpression "pkgs.alacritty"; }; terminalCommand = mkOption { type = types.str; default = ""; description = '' Custom command to run in the terminal. Placeholders: - %s = project path - %a = project args (from projectDirs..args) If empty, defaults to: cd to project, run "opencode %a" Examples: - "" (empty) - Uses default: cd to project, run opencode with args - "opencode %a" - Run opencode with project-specific args - "nvim" - Open editor (no args) - "myapp %s %a" - Custom app with path and args ''; example = literalExpression ''"opencode %a"''; }; rofiPrompt = mkOption { type = types.str; default = "Select project"; description = "Prompt text displayed in rofi."; example = "Open project:"; }; rofiArgs = mkOption { type = types.listOf types.str; default = ["-dmenu" "-i"]; description = '' Arguments to pass to rofi. Common options: - "-dmenu" - Enable dmenu mode (required) - "-i" - Case-insensitive matching - "-theme " - Use specific rofi theme - "-width " - Window width - "-lines " - Number of visible lines ''; example = literalExpression ''["-dmenu" "-i" "-theme gruvbox"]''; }; }; config = mkIf cfg.enable { home.packages = [pkgs.rofi-project-opener]; # Write JSON config file for project directories xdg.configFile."rofi-project-opener/projects.json".text = projectDirsJson; # Write shell config file for other settings xdg.configFile."rofi-project-opener/config".text = '' # rofi-project-opener configuration TERMINAL="${ if isDerivation cfg.terminal then "${cfg.terminal}/bin/${cfg.terminal.pname or (builtins.baseNameOf (toString cfg.terminal))}" else cfg.terminal }" ${optionalString (cfg.terminalCommand != "") ''TERMINAL_CMD="${cfg.terminalCommand}"''} ROFI_PROMPT="${cfg.rofiPrompt}" ROFI_ARGS="${escapeShellArgs cfg.rofiArgs}" ''; }; }