From c3d3be60ac0246b04f609a57a25beff2a3cf80ed Mon Sep 17 00:00:00 2001 From: joshp123 Date: Sat, 14 Feb 2026 23:24:11 -0800 Subject: [PATCH] style: nix fmt (nixfmt-tree) + exclude generated config options --- examples/hello-world-plugin/flake.nix | 17 +- flake.nix | 6 +- nix/checks/openclaw-config-validity.nix | 135 ++++--- nix/checks/openclaw-default-instance.nix | 109 +++--- nix/checks/openclaw-hm-activation.nix | 6 +- nix/checks/openclaw-package-contents.nix | 6 +- nix/modules/home-manager/openclaw/config.nix | 356 +++++++++++------- nix/modules/home-manager/openclaw/default.nix | 17 +- nix/modules/home-manager/openclaw/files.nix | 219 ++++++----- nix/modules/home-manager/openclaw/lib.nix | 94 ++--- .../openclaw/options-instance.nix | 56 +-- nix/modules/home-manager/openclaw/options.nix | 110 +++--- nix/modules/home-manager/openclaw/plugins.nix | 293 +++++++------- nix/overlay.nix | 9 +- nix/packages/default.nix | 17 +- nix/packages/openclaw-app.nix | 7 +- nix/packages/openclaw-batteries.nix | 11 +- nix/tests/hm-activation-macos/flake.nix | 58 +-- nix/tools/extended.nix | 26 +- templates/agent-first/flake.nix | 20 +- 20 files changed, 908 insertions(+), 664 deletions(-) diff --git a/examples/hello-world-plugin/flake.nix b/examples/hello-world-plugin/flake.nix index 2b878e0..c670118 100644 --- a/examples/hello-world-plugin/flake.nix +++ b/examples/hello-world-plugin/flake.nix @@ -6,11 +6,18 @@ flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { self, nixpkgs, flake-utils }: - flake-utils.lib.eachDefaultSystem (system: + outputs = + { + self, + nixpkgs, + flake-utils, + }: + flake-utils.lib.eachDefaultSystem ( + system: let pkgs = import nixpkgs { inherit system; }; - in { + in + { packages.default = pkgs.buildGoModule { pname = "hello-world"; version = "0.1.0"; @@ -27,8 +34,8 @@ skills = [ ./skills/hello-world ]; packages = [ self.packages.${system}.default ]; needs = { - stateDirs = []; - requiredEnv = []; + stateDirs = [ ]; + requiredEnv = [ ]; }; }; } diff --git a/flake.nix b/flake.nix index 11829a4..83b5073 100644 --- a/flake.nix +++ b/flake.nix @@ -51,7 +51,11 @@ }; in { - formatter = pkgs.nixfmt-tree; + formatter = pkgs.nixfmt-tree.override { + settings = { + global.excludes = [ "nix/generated/openclaw-config-options.nix" ]; + }; + }; packages = packageSetStable // { default = packageSetStable.openclaw; diff --git a/nix/checks/openclaw-config-validity.nix b/nix/checks/openclaw-config-validity.nix index 5124899..023f1c5 100644 --- a/nix/checks/openclaw-config-validity.nix +++ b/nix/checks/openclaw-config-validity.nix @@ -1,86 +1,97 @@ -{ lib, pkgs, stdenv, nodejs_22, openclawGateway }: +{ + lib, + pkgs, + stdenv, + nodejs_22, + openclawGateway, +}: let - stubModule = { lib, ... }: { - options = { - assertions = lib.mkOption { - type = lib.types.listOf lib.types.attrs; - default = []; - }; + stubModule = + { lib, ... }: + { + options = { + assertions = lib.mkOption { + type = lib.types.listOf lib.types.attrs; + default = [ ]; + }; - home.homeDirectory = lib.mkOption { - type = lib.types.str; - default = "/tmp"; - }; + home.homeDirectory = lib.mkOption { + type = lib.types.str; + default = "/tmp"; + }; - home.packages = lib.mkOption { - type = lib.types.listOf lib.types.anything; - default = []; - }; + home.packages = lib.mkOption { + type = lib.types.listOf lib.types.anything; + default = [ ]; + }; - home.file = lib.mkOption { - type = lib.types.attrs; - default = {}; - }; + home.file = lib.mkOption { + type = lib.types.attrs; + default = { }; + }; - home.activation = lib.mkOption { - type = lib.types.attrs; - default = {}; - }; + home.activation = lib.mkOption { + type = lib.types.attrs; + default = { }; + }; - launchd.agents = lib.mkOption { - type = lib.types.attrs; - default = {}; - }; + launchd.agents = lib.mkOption { + type = lib.types.attrs; + default = { }; + }; - systemd.user.services = lib.mkOption { - type = lib.types.attrs; - default = {}; - }; + systemd.user.services = lib.mkOption { + type = lib.types.attrs; + default = { }; + }; - programs.git.enable = lib.mkOption { - type = lib.types.bool; - default = false; - }; + programs.git.enable = lib.mkOption { + type = lib.types.bool; + default = false; + }; - lib = lib.mkOption { - type = lib.types.attrs; - default = {}; + lib = lib.mkOption { + type = lib.types.attrs; + default = { }; + }; }; }; - }; moduleEval = lib.evalModules { modules = [ stubModule ../modules/home-manager/openclaw.nix - ({ lib, ... }: { - config = { - home.homeDirectory = "/tmp"; - programs.git.enable = false; - lib.file.mkOutOfStoreSymlink = path: path; - programs.openclaw = { - enable = true; - launchd.enable = false; - systemd.enable = false; - instances.default = {}; - config = { - gateway = { - bind = "tailnet"; - auth = { - mode = "token"; - token = "test-token"; - }; - reload = { - mode = "hot"; - debounceMs = 500; + ( + { lib, ... }: + { + config = { + home.homeDirectory = "/tmp"; + programs.git.enable = false; + lib.file.mkOutOfStoreSymlink = path: path; + programs.openclaw = { + enable = true; + launchd.enable = false; + systemd.enable = false; + instances.default = { }; + config = { + gateway = { + bind = "tailnet"; + auth = { + mode = "token"; + token = "test-token"; + }; + reload = { + mode = "hot"; + debounceMs = 500; + }; }; + discovery.mdns.mode = "minimal"; }; - discovery.mdns.mode = "minimal"; }; }; - }; - }) + } + ) ]; specialArgs = { inherit pkgs; }; }; diff --git a/nix/checks/openclaw-default-instance.nix b/nix/checks/openclaw-default-instance.nix index d4650f9..31bcd0f 100644 --- a/nix/checks/openclaw-default-instance.nix +++ b/nix/checks/openclaw-default-instance.nix @@ -1,71 +1,80 @@ -{ lib, pkgs, stdenv }: +{ + lib, + pkgs, + stdenv, +}: let - stubModule = { lib, ... }: { - options = { - assertions = lib.mkOption { - type = lib.types.listOf lib.types.attrs; - default = []; - }; + stubModule = + { lib, ... }: + { + options = { + assertions = lib.mkOption { + type = lib.types.listOf lib.types.attrs; + default = [ ]; + }; - home.homeDirectory = lib.mkOption { - type = lib.types.str; - default = "/tmp"; - }; + home.homeDirectory = lib.mkOption { + type = lib.types.str; + default = "/tmp"; + }; - home.packages = lib.mkOption { - type = lib.types.listOf lib.types.anything; - default = []; - }; + home.packages = lib.mkOption { + type = lib.types.listOf lib.types.anything; + default = [ ]; + }; - home.file = lib.mkOption { - type = lib.types.attrs; - default = {}; - }; + home.file = lib.mkOption { + type = lib.types.attrs; + default = { }; + }; - home.activation = lib.mkOption { - type = lib.types.attrs; - default = {}; - }; + home.activation = lib.mkOption { + type = lib.types.attrs; + default = { }; + }; - launchd.agents = lib.mkOption { - type = lib.types.attrs; - default = {}; - }; + launchd.agents = lib.mkOption { + type = lib.types.attrs; + default = { }; + }; - systemd.user.services = lib.mkOption { - type = lib.types.attrs; - default = {}; - }; + systemd.user.services = lib.mkOption { + type = lib.types.attrs; + default = { }; + }; - programs.git.enable = lib.mkOption { - type = lib.types.bool; - default = false; - }; + programs.git.enable = lib.mkOption { + type = lib.types.bool; + default = false; + }; - lib = lib.mkOption { - type = lib.types.attrs; - default = {}; + lib = lib.mkOption { + type = lib.types.attrs; + default = { }; + }; }; }; - }; eval = lib.evalModules { modules = [ stubModule ../modules/home-manager/openclaw.nix - ({ lib, ... }: { - config = { - home.homeDirectory = "/tmp"; - programs.git.enable = false; - lib.file.mkOutOfStoreSymlink = path: path; - programs.openclaw = { - enable = true; - launchd.enable = false; - systemd.enable = true; + ( + { lib, ... }: + { + config = { + home.homeDirectory = "/tmp"; + programs.git.enable = false; + lib.file.mkOutOfStoreSymlink = path: path; + programs.openclaw = { + enable = true; + launchd.enable = false; + systemd.enable = true; + }; }; - }; - }) + } + ) ]; specialArgs = { inherit pkgs; }; }; diff --git a/nix/checks/openclaw-hm-activation.nix b/nix/checks/openclaw-hm-activation.nix index 23976c2..bab172e 100644 --- a/nix/checks/openclaw-hm-activation.nix +++ b/nix/checks/openclaw-hm-activation.nix @@ -8,7 +8,8 @@ in pkgs.testers.nixosTest { name = "openclaw-hm-activation"; - nodes.machine = { ... }: + nodes.machine = + { ... }: { imports = [ home-manager.nixosModules.home-manager ]; @@ -23,7 +24,8 @@ pkgs.testers.nixosTest { home-manager = { useGlobalPkgs = true; useUserPackages = true; - users.alice = { lib, ... }: + users.alice = + { lib, ... }: { imports = [ openclawModule ]; diff --git a/nix/checks/openclaw-package-contents.nix b/nix/checks/openclaw-package-contents.nix index 6b0008b..2b13b12 100644 --- a/nix/checks/openclaw-package-contents.nix +++ b/nix/checks/openclaw-package-contents.nix @@ -1,4 +1,8 @@ -{ lib, stdenv, openclawGateway }: +{ + lib, + stdenv, + openclawGateway, +}: stdenv.mkDerivation { pname = "openclaw-package-contents"; diff --git a/nix/modules/home-manager/openclaw/config.nix b/nix/modules/home-manager/openclaw/config.nix index f352455..dcf9ffd 100644 --- a/nix/modules/home-manager/openclaw/config.nix +++ b/nix/modules/home-manager/openclaw/config.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let openclawLib = import ./lib.nix { inherit config lib pkgs; }; @@ -19,7 +24,7 @@ let launchd = cfg.launchd; systemd = cfg.systemd; plugins = openclawLib.effectivePlugins; - config = {}; + config = { }; appDefaults = { enable = true; attachExistingOnly = true; @@ -32,20 +37,38 @@ let }; }; - instances = if cfg.instances != {} - then cfg.instances - else lib.optionalAttrs cfg.enable { default = defaultInstance; }; + instances = + if cfg.instances != { } then + cfg.instances + else + lib.optionalAttrs cfg.enable { default = defaultInstance; }; enabledInstances = lib.filterAttrs (_: inst: inst.enable) instances; - plugins = import ./plugins.nix { inherit lib pkgs openclawLib enabledInstances; }; - - files = import ./files.nix { - inherit config lib pkgs openclawLib enabledInstances plugins; + plugins = import ./plugins.nix { + inherit + lib + pkgs + openclawLib + enabledInstances + ; }; - stripNulls = value: - if value == null then null + files = import ./files.nix { + inherit + config + lib + pkgs + openclawLib + enabledInstances + plugins + ; + }; + + stripNulls = + value: + if value == null then + null else if builtins.isAttrs value then lib.filterAttrs (_: v: v != null) (builtins.mapAttrs (_: stripNulls) value) else if builtins.isList value then @@ -59,154 +82,183 @@ let }; }; - mkInstanceConfig = name: inst: let - gatewayPackage = - if inst.gatewayPath != null then - pkgs.callPackage ../../packages/openclaw-gateway.nix { - gatewaySrc = builtins.path { - path = inst.gatewayPath; - name = "openclaw-gateway-src"; - }; - pnpmDepsHash = inst.gatewayPnpmDepsHash; - } - else - inst.package; - pluginPackages = plugins.pluginPackagesFor name; - pluginEnvAll = plugins.pluginEnvAllFor name; - mergedConfig0 = stripNulls (lib.recursiveUpdate (lib.recursiveUpdate baseConfig cfg.config) inst.config); - existingWorkspace = (((mergedConfig0.agents or {}).defaults or {}).workspace or null); - mergedConfig = - if (cfg.workspace.pinAgentDefaults or true) && existingWorkspace == null then - lib.recursiveUpdate mergedConfig0 { agents = { defaults = { workspace = inst.workspaceDir; }; }; } - else - mergedConfig0; - configJson = builtins.toJSON mergedConfig; - configFile = pkgs.writeText "openclaw-${name}.json" configJson; - gatewayWrapper = pkgs.writeShellScriptBin "openclaw-gateway-${name}" '' - set -euo pipefail - - if [ -n "${lib.makeBinPath pluginPackages}" ]; then - export PATH="${lib.makeBinPath pluginPackages}:$PATH" - fi - - ${lib.concatStringsSep "\n" (map (entry: - let - isFile = lib.hasSuffix "_FILE" entry.key; - in '' - if [ -f "${entry.value}" ]; then - if ${if isFile then "true" else "false"}; then - export ${entry.key}="${entry.value}" - else - rawValue="$("${lib.getExe' pkgs.coreutils "cat"}" "${entry.value}")" - if [ "''${rawValue#${entry.key}=}" != "$rawValue" ]; then - export ${entry.key}="''${rawValue#${entry.key}=}" - else - export ${entry.key}="$rawValue" - fi - fi + mkInstanceConfig = + name: inst: + let + gatewayPackage = + if inst.gatewayPath != null then + pkgs.callPackage ../../packages/openclaw-gateway.nix { + gatewaySrc = builtins.path { + path = inst.gatewayPath; + name = "openclaw-gateway-src"; + }; + pnpmDepsHash = inst.gatewayPnpmDepsHash; + } else - export ${entry.key}="${entry.value}" + inst.package; + pluginPackages = plugins.pluginPackagesFor name; + pluginEnvAll = plugins.pluginEnvAllFor name; + mergedConfig0 = stripNulls ( + lib.recursiveUpdate (lib.recursiveUpdate baseConfig cfg.config) inst.config + ); + existingWorkspace = (((mergedConfig0.agents or { }).defaults or { }).workspace or null); + mergedConfig = + if (cfg.workspace.pinAgentDefaults or true) && existingWorkspace == null then + lib.recursiveUpdate mergedConfig0 { + agents = { + defaults = { + workspace = inst.workspaceDir; + }; + }; + } + else + mergedConfig0; + configJson = builtins.toJSON mergedConfig; + configFile = pkgs.writeText "openclaw-${name}.json" configJson; + gatewayWrapper = pkgs.writeShellScriptBin "openclaw-gateway-${name}" '' + set -euo pipefail + + if [ -n "${lib.makeBinPath pluginPackages}" ]; then + export PATH="${lib.makeBinPath pluginPackages}:$PATH" fi - '') pluginEnvAll)} - exec "${gatewayPackage}/bin/openclaw" "$@" - ''; - appDefaults = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.appDefaults.enable) { - attachExistingOnly = inst.appDefaults.attachExistingOnly; - gatewayPort = inst.gatewayPort; - nixMode = inst.appDefaults.nixMode; - }; + ${lib.concatStringsSep "\n" ( + map ( + entry: + let + isFile = lib.hasSuffix "_FILE" entry.key; + in + '' + if [ -f "${entry.value}" ]; then + if ${if isFile then "true" else "false"}; then + export ${entry.key}="${entry.value}" + else + rawValue="$("${lib.getExe' pkgs.coreutils "cat"}" "${entry.value}")" + if [ "''${rawValue#${entry.key}=}" != "$rawValue" ]; then + export ${entry.key}="''${rawValue#${entry.key}=}" + else + export ${entry.key}="$rawValue" + fi + fi + else + export ${entry.key}="${entry.value}" + fi + '' + ) pluginEnvAll + )} - appInstall = if !(pkgs.stdenv.hostPlatform.isDarwin && inst.app.install.enable && appPackage != null) then - null - else { - name = lib.removePrefix "${homeDir}/" inst.app.install.path; - value = { - source = "${appPackage}/Applications/OpenClaw.app"; - recursive = true; - force = true; + exec "${gatewayPackage}/bin/openclaw" "$@" + ''; + appDefaults = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.appDefaults.enable) { + attachExistingOnly = inst.appDefaults.attachExistingOnly; + gatewayPort = inst.gatewayPort; + nixMode = inst.appDefaults.nixMode; }; - }; - package = gatewayPackage; - in { - homeFile = { - name = openclawLib.toRelative inst.configPath; - value = { text = configJson; }; - }; - configFile = configFile; - configPath = inst.configPath; + appInstall = + if !(pkgs.stdenv.hostPlatform.isDarwin && inst.app.install.enable && appPackage != null) then + null + else + { + name = lib.removePrefix "${homeDir}/" inst.app.install.path; + value = { + source = "${appPackage}/Applications/OpenClaw.app"; + recursive = true; + force = true; + }; + }; - dirs = [ inst.stateDir inst.workspaceDir (builtins.dirOf inst.logPath) ]; + package = gatewayPackage; + in + { + homeFile = { + name = openclawLib.toRelative inst.configPath; + value = { + text = configJson; + }; + }; + configFile = configFile; + configPath = inst.configPath; - launchdAgent = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.launchd.enable) { - "${inst.launchd.label}" = { - enable = true; - config = { - Label = inst.launchd.label; - ProgramArguments = [ - "${gatewayWrapper}/bin/openclaw-gateway-${name}" - "gateway" - "--port" - "${toString inst.gatewayPort}" - ]; - RunAtLoad = true; - KeepAlive = true; - WorkingDirectory = inst.stateDir; - StandardOutPath = inst.logPath; - StandardErrorPath = inst.logPath; - EnvironmentVariables = { - HOME = homeDir; - OPENCLAW_CONFIG_PATH = inst.configPath; - OPENCLAW_STATE_DIR = inst.stateDir; - OPENCLAW_IMAGE_BACKEND = "sips"; - OPENCLAW_NIX_MODE = "1"; + dirs = [ + inst.stateDir + inst.workspaceDir + (builtins.dirOf inst.logPath) + ]; + + launchdAgent = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.launchd.enable) { + "${inst.launchd.label}" = { + enable = true; + config = { + Label = inst.launchd.label; + ProgramArguments = [ + "${gatewayWrapper}/bin/openclaw-gateway-${name}" + "gateway" + "--port" + "${toString inst.gatewayPort}" + ]; + RunAtLoad = true; + KeepAlive = true; + WorkingDirectory = inst.stateDir; + StandardOutPath = inst.logPath; + StandardErrorPath = inst.logPath; + EnvironmentVariables = { + HOME = homeDir; + OPENCLAW_CONFIG_PATH = inst.configPath; + OPENCLAW_STATE_DIR = inst.stateDir; + OPENCLAW_IMAGE_BACKEND = "sips"; + OPENCLAW_NIX_MODE = "1"; + }; }; }; }; - }; - systemdService = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux && inst.systemd.enable) { - "${inst.systemd.unitName}" = { - Unit = { - Description = "OpenClaw gateway (${name})"; - }; - Service = { - ExecStart = "${gatewayWrapper}/bin/openclaw-gateway-${name} gateway --port ${toString inst.gatewayPort}"; - WorkingDirectory = inst.stateDir; - Restart = "always"; - RestartSec = "1s"; - Environment = [ - "HOME=${homeDir}" - "OPENCLAW_CONFIG_PATH=${inst.configPath}" - "OPENCLAW_STATE_DIR=${inst.stateDir}" - "OPENCLAW_NIX_MODE=1" - ]; - StandardOutput = "append:${inst.logPath}"; - StandardError = "append:${inst.logPath}"; + systemdService = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux && inst.systemd.enable) { + "${inst.systemd.unitName}" = { + Unit = { + Description = "OpenClaw gateway (${name})"; + }; + Service = { + ExecStart = "${gatewayWrapper}/bin/openclaw-gateway-${name} gateway --port ${toString inst.gatewayPort}"; + WorkingDirectory = inst.stateDir; + Restart = "always"; + RestartSec = "1s"; + Environment = [ + "HOME=${homeDir}" + "OPENCLAW_CONFIG_PATH=${inst.configPath}" + "OPENCLAW_STATE_DIR=${inst.stateDir}" + "OPENCLAW_NIX_MODE=1" + ]; + StandardOutput = "append:${inst.logPath}"; + StandardError = "append:${inst.logPath}"; + }; }; }; - }; - appDefaults = appDefaults; - appInstall = appInstall; - package = package; - }; + appDefaults = appDefaults; + appInstall = appInstall; + package = package; + }; instanceConfigs = lib.mapAttrsToList mkInstanceConfig enabledInstances; appInstalls = lib.filter (item: item != null) (map (item: item.appInstall) instanceConfigs); - appDefaults = lib.foldl' (acc: item: lib.recursiveUpdate acc item.appDefaults) {} instanceConfigs; + appDefaults = lib.foldl' (acc: item: lib.recursiveUpdate acc item.appDefaults) { } instanceConfigs; appDefaultsEnabled = lib.filterAttrs (_: inst: inst.appDefaults.enable) enabledInstances; -in { - config = lib.mkIf (cfg.enable || cfg.instances != {}) { +in +{ + config = lib.mkIf (cfg.enable || cfg.instances != { }) { assertions = [ { assertion = lib.length (lib.attrNames appDefaultsEnabled) <= 1; message = "Only one OpenClaw instance may enable appDefaults."; } - ] ++ files.documentsAssertions ++ files.skillAssertions ++ plugins.pluginAssertions ++ plugins.pluginSkillAssertions; + ] + ++ files.documentsAssertions + ++ files.skillAssertions + ++ plugins.pluginAssertions + ++ plugins.pluginSkillAssertions; home.packages = lib.unique ( (map (item: item.package) instanceConfigs) @@ -242,12 +294,20 @@ in { ); home.activation.openclawDirs = lib.hm.dag.entryAfter [ "writeBoundary" ] '' - run --quiet ${lib.getExe' pkgs.coreutils "mkdir"} -p ${lib.concatStringsSep " " (lib.concatMap (item: item.dirs) instanceConfigs)} - ${lib.optionalString (plugins.pluginStateDirsAll != []) "run --quiet ${lib.getExe' pkgs.coreutils "mkdir"} -p ${lib.concatStringsSep " " plugins.pluginStateDirsAll}"} + run --quiet ${lib.getExe' pkgs.coreutils "mkdir"} -p ${ + lib.concatStringsSep " " (lib.concatMap (item: item.dirs) instanceConfigs) + } + ${lib.optionalString (plugins.pluginStateDirsAll != [ ]) + "run --quiet ${lib.getExe' pkgs.coreutils "mkdir"} -p ${lib.concatStringsSep " " plugins.pluginStateDirsAll}" + } ''; home.activation.openclawConfigFiles = lib.hm.dag.entryAfter [ "openclawDirs" ] '' - ${lib.concatStringsSep "\n" (map (item: "run --quiet ${lib.getExe' pkgs.coreutils "ln"} -sfn ${item.configFile} ${item.configPath}") instanceConfigs)} + ${lib.concatStringsSep "\n" ( + map ( + item: "run --quiet ${lib.getExe' pkgs.coreutils "ln"} -sfn ${item.configFile} ${item.configPath}" + ) instanceConfigs + )} ''; home.activation.openclawPluginGuard = lib.hm.dag.entryAfter [ "writeBoundary" ] '' @@ -255,14 +315,22 @@ in { ${plugins.pluginGuards} ''; - home.activation.openclawAppDefaults = lib.mkIf (pkgs.stdenv.hostPlatform.isDarwin && appDefaults != {}) ( - lib.hm.dag.entryAfter [ "writeBoundary" ] '' - # Nix mode + app defaults (OpenClaw.app) - /usr/bin/defaults write ai.openclaw.mac openclaw.nixMode -bool ${lib.boolToString (appDefaults.nixMode or true)} - /usr/bin/defaults write ai.openclaw.mac openclaw.gateway.attachExistingOnly -bool ${lib.boolToString (appDefaults.attachExistingOnly or true)} - /usr/bin/defaults write ai.openclaw.mac gatewayPort -int ${toString (appDefaults.gatewayPort or 18789)} - '' - ); + home.activation.openclawAppDefaults = + lib.mkIf (pkgs.stdenv.hostPlatform.isDarwin && appDefaults != { }) + ( + lib.hm.dag.entryAfter [ "writeBoundary" ] '' + # Nix mode + app defaults (OpenClaw.app) + /usr/bin/defaults write ai.openclaw.mac openclaw.nixMode -bool ${ + lib.boolToString (appDefaults.nixMode or true) + } + /usr/bin/defaults write ai.openclaw.mac openclaw.gateway.attachExistingOnly -bool ${ + lib.boolToString (appDefaults.attachExistingOnly or true) + } + /usr/bin/defaults write ai.openclaw.mac gatewayPort -int ${ + toString (appDefaults.gatewayPort or 18789) + } + '' + ); home.activation.openclawLaunchdRelink = lib.mkIf pkgs.stdenv.hostPlatform.isDarwin ( lib.hm.dag.entryAfter [ "linkGeneration" ] '' diff --git a/nix/modules/home-manager/openclaw/default.nix b/nix/modules/home-manager/openclaw/default.nix index d434464..1db9f3b 100644 --- a/nix/modules/home-manager/openclaw/default.nix +++ b/nix/modules/home-manager/openclaw/default.nix @@ -1,8 +1,19 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: { imports = [ - (lib.mkRenamedOptionModule [ "programs" "openclaw" "firstParty" ] [ "programs" "openclaw" "bundledPlugins" ]) - (lib.mkRenamedOptionModule [ "programs" "openclaw" "plugins" ] [ "programs" "openclaw" "customPlugins" ]) + (lib.mkRenamedOptionModule + [ "programs" "openclaw" "firstParty" ] + [ "programs" "openclaw" "bundledPlugins" ] + ) + (lib.mkRenamedOptionModule + [ "programs" "openclaw" "plugins" ] + [ "programs" "openclaw" "customPlugins" ] + ) ./options.nix ./config.nix ]; diff --git a/nix/modules/home-manager/openclaw/files.nix b/nix/modules/home-manager/openclaw/files.nix index e82fadd..5774a69 100644 --- a/nix/modules/home-manager/openclaw/files.nix +++ b/nix/modules/home-manager/openclaw/files.nix @@ -1,4 +1,11 @@ -{ config, lib, pkgs, openclawLib, enabledInstances, plugins }: +{ + config, + lib, + pkgs, + openclawLib, + enabledInstances, + plugins, +}: let cfg = openclawLib.cfg; @@ -8,7 +15,8 @@ let documentsEnabled = cfg.documents != null; instanceWorkspaceDirs = map (inst: resolvePath inst.workspaceDir) (lib.attrValues enabledInstances); - renderSkill = skill: + renderSkill = + skill: let frontmatterLines = [ "---" @@ -24,15 +32,18 @@ let frontmatter = lib.concatStringsSep "\n" frontmatterLines; body = if skill ? body then skill.body else ""; in - "${frontmatter}\n\n${body}\n"; + "${frontmatter}\n\n${body}\n"; skillAssertions = let names = map (skill: skill.name) cfg.skills; - nameCounts = lib.foldl' (acc: name: acc // { "${name}" = (acc.${name} or 0) + 1; }) {} names; + nameCounts = lib.foldl' (acc: name: acc // { "${name}" = (acc.${name} or 0) + 1; }) { } names; duplicateNames = lib.attrNames (lib.filterAttrs (_: v: v > 1) nameCounts); in - if duplicateNames == [] then [] else [ + if duplicateNames == [ ] then + [ ] + else + [ { assertion = false; message = "programs.openclaw.skills has duplicate names: ${lib.concatStringsSep ", " duplicateNames}"; @@ -41,48 +52,56 @@ let skillFiles = let - entriesForInstance = instName: inst: + entriesForInstance = + instName: inst: let base = "${toRelative (resolvePath inst.workspaceDir)}/skills"; - entryFor = skill: + entryFor = + skill: let mode = skill.mode or "symlink"; source = if skill ? source && skill.source != null then resolvePath skill.source else null; in - if mode == "inline" then - { - name = "${base}/${skill.name}/SKILL.md"; - value = { text = renderSkill skill; }; - } - else if mode == "copy" then - { - name = "${base}/${skill.name}"; - value = { - source = builtins.path { - name = "openclaw-skill-${skill.name}"; - path = source; - }; - recursive = true; - }; - } - else - { - name = "${base}/${skill.name}"; - value = { - source = config.lib.file.mkOutOfStoreSymlink source; - recursive = true; - }; + if mode == "inline" then + { + name = "${base}/${skill.name}/SKILL.md"; + value = { + text = renderSkill skill; }; - pluginEntriesFor = p: + } + else if mode == "copy" then + { + name = "${base}/${skill.name}"; + value = { + source = builtins.path { + name = "openclaw-skill-${skill.name}"; + path = source; + }; + recursive = true; + }; + } + else + { + name = "${base}/${skill.name}"; + value = { + source = config.lib.file.mkOutOfStoreSymlink source; + recursive = true; + }; + }; + pluginEntriesFor = + p: map (skillPath: { name = "${base}/${builtins.baseNameOf skillPath}"; - value = { source = skillPath; recursive = true; }; + value = { + source = skillPath; + recursive = true; + }; }) p.skills; - pluginsForInstance = plugins.resolvedPluginsByInstance.${instName} or []; + pluginsForInstance = plugins.resolvedPluginsByInstance.${instName} or [ ]; in - (map entryFor cfg.skills) ++ (lib.flatten (map pluginEntriesFor pluginsForInstance)); + (map entryFor cfg.skills) ++ (lib.flatten (map pluginEntriesFor pluginsForInstance)); in - lib.listToAttrs (lib.flatten (lib.mapAttrsToList entriesForInstance enabledInstances)); + lib.listToAttrs (lib.flatten (lib.mapAttrsToList entriesForInstance enabledInstances)); documentsRequiredFiles = [ "AGENTS.md" @@ -103,9 +122,9 @@ let let extra = lib.filter (file: builtins.pathExists (cfg.documents + "/${file}")) documentsOptionalFiles; in - documentsRequiredFiles ++ extra + documentsRequiredFiles ++ extra else - []; + [ ]; documentsAssertions = lib.optionals documentsEnabled [ { @@ -126,77 +145,74 @@ let } ]; - documentsGuard = - lib.optionalString documentsEnabled ( - let - guardLine = file: '' - if [ -e "${file}" ] && [ ! -L "${file}" ]; then - echo "OpenClaw documents are managed by Nix. Please adopt ${file} into your documents directory and re-run." >&2 - exit 1 - fi - ''; - guardForDir = dir: '' - ${lib.concatStringsSep "\n" (map (name: guardLine "${dir}/${name}") documentsFileNames)} - ''; - in - lib.concatStringsSep "\n" (map guardForDir instanceWorkspaceDirs) - ); + documentsGuard = lib.optionalString documentsEnabled ( + let + guardLine = file: '' + if [ -e "${file}" ] && [ ! -L "${file}" ]; then + echo "OpenClaw documents are managed by Nix. Please adopt ${file} into your documents directory and re-run." >&2 + exit 1 + fi + ''; + guardForDir = dir: '' + ${lib.concatStringsSep "\n" (map (name: guardLine "${dir}/${name}") documentsFileNames)} + ''; + in + lib.concatStringsSep "\n" (map guardForDir instanceWorkspaceDirs) + ); toolsReport = if documentsEnabled then let - toolNames = toolSets.toolNames or []; - renderPkgName = pkg: - if pkg ? pname then pkg.pname else lib.getName pkg; - renderPlugin = plugin: - let - pkgNames = map renderPkgName (lib.filter (p: p != null) plugin.packages); - pkgSuffix = - if pkgNames == [] - then "" - else " — " + (lib.concatStringsSep ", " pkgNames); - in - "- " + plugin.name + pkgSuffix + " (" + plugin.source + ")"; - pluginLinesFor = instName: inst: - let - pluginsForInstance = plugins.resolvedPluginsByInstance.${instName} or []; - lines = if pluginsForInstance == [] then [ "- (none)" ] else map renderPlugin pluginsForInstance; - in - [ - "" - "### Instance: ${instName}" - ] ++ lines; - reportLines = + toolNames = toolSets.toolNames or [ ]; + renderPkgName = pkg: if pkg ? pname then pkg.pname else lib.getName pkg; + renderPlugin = + plugin: + let + pkgNames = map renderPkgName (lib.filter (p: p != null) plugin.packages); + pkgSuffix = if pkgNames == [ ] then "" else " — " + (lib.concatStringsSep ", " pkgNames); + in + "- " + plugin.name + pkgSuffix + " (" + plugin.source + ")"; + pluginLinesFor = + instName: inst: + let + pluginsForInstance = plugins.resolvedPluginsByInstance.${instName} or [ ]; + lines = if pluginsForInstance == [ ] then [ "- (none)" ] else map renderPlugin pluginsForInstance; + in [ - "" "" - "## Nix-managed tools" - "" - "### Built-in toolchain" + "### Instance: ${instName}" ] - ++ (if toolNames == [] then [ "- (none)" ] else map (name: "- " + name) toolNames) - ++ [ - "" - "## Nix-managed plugin report" - "" - "Plugins enabled per instance (last-wins on name collisions):" - ] - ++ lib.concatLists (lib.mapAttrsToList pluginLinesFor enabledInstances) - ++ [ - "" - "Tools: batteries-included toolchain + plugin-provided CLIs." - "" - "" - ]; + ++ lines; + reportLines = [ + "" + "" + "## Nix-managed tools" + "" + "### Built-in toolchain" + ] + ++ (if toolNames == [ ] then [ "- (none)" ] else map (name: "- " + name) toolNames) + ++ [ + "" + "## Nix-managed plugin report" + "" + "Plugins enabled per instance (last-wins on name collisions):" + ] + ++ lib.concatLists (lib.mapAttrsToList pluginLinesFor enabledInstances) + ++ [ + "" + "Tools: batteries-included toolchain + plugin-provided CLIs." + "" + "" + ]; reportText = lib.concatStringsSep "\n" reportLines; in - pkgs.writeText "openclaw-tools-report.md" reportText + pkgs.writeText "openclaw-tools-report.md" reportText else null; toolsWithReport = if documentsEnabled then - pkgs.runCommand "openclaw-tools-with-report.md" {} '' + pkgs.runCommand "openclaw-tools-with-report.md" { } '' cat ${cfg.documents + "/TOOLS.md"} > $out echo "" >> $out cat ${toolsReport} >> $out @@ -207,7 +223,8 @@ let documentsFiles = if documentsEnabled then let - mkDocFiles = dir: + mkDocFiles = + dir: let mkDoc = name: { name = toRelative (dir + "/${name}"); @@ -216,18 +233,20 @@ let }; }; in - lib.listToAttrs (map mkDoc documentsFileNames); + lib.listToAttrs (map mkDoc documentsFileNames); in - lib.mkMerge (map mkDocFiles instanceWorkspaceDirs) + lib.mkMerge (map mkDocFiles instanceWorkspaceDirs) else - {}; + { }; -in { +in +{ inherit documentsEnabled documentsAssertions documentsGuard documentsFiles skillAssertions - skillFiles; + skillFiles + ; } diff --git a/nix/modules/home-manager/openclaw/lib.nix b/nix/modules/home-manager/openclaw/lib.nix index 4a2c784..9ec609f 100644 --- a/nix/modules/home-manager/openclaw/lib.nix +++ b/nix/modules/home-manager/openclaw/lib.nix @@ -1,4 +1,8 @@ -{ config, lib, pkgs }: +{ + config, + lib, + pkgs, +}: let cfg = config.programs.openclaw; @@ -9,59 +13,62 @@ let toolNamesOverride = cfg.toolNames; excludeToolNames = effectiveExcludeTools; }; - toolOverridesEnabled = cfg.toolNames != null || effectiveExcludeTools != []; + toolOverridesEnabled = cfg.toolNames != null || effectiveExcludeTools != [ ]; toolSets = import ../../../tools/extended.nix ({ inherit pkgs; } // toolOverrides); defaultPackage = - if toolOverridesEnabled && cfg.package == pkgs.openclaw - then (pkgs.openclawPackages.withTools toolOverrides).openclaw - else cfg.package; + if toolOverridesEnabled && cfg.package == pkgs.openclaw then + (pkgs.openclawPackages.withTools toolOverrides).openclaw + else + cfg.package; appPackage = if cfg.appPackage != null then cfg.appPackage else defaultPackage; generatedConfigOptions = import ../../../generated/openclaw-config-options.nix { lib = lib; }; - bundledPluginSources = let - stepieteRev = "983210e3b6e9285780e87f48ce9354b51a270e95"; - stepieteNarHash = "sha256-fY8t41kMSHu2ovf89mIdvC7vkceroCwKxw/MKVn4rsE="; - stepiete = tool: - "github:openclaw/nix-steipete-tools?dir=tools/${tool}&rev=${stepieteRev}&narHash=${stepieteNarHash}"; - in { - summarize = stepiete "summarize"; - peekaboo = stepiete "peekaboo"; - oracle = stepiete "oracle"; - poltergeist = stepiete "poltergeist"; - sag = stepiete "sag"; - camsnap = stepiete "camsnap"; - gogcli = stepiete "gogcli"; - goplaces = stepiete "goplaces"; - bird = stepiete "bird"; - sonoscli = stepiete "sonoscli"; - imsg = stepiete "imsg"; - }; - - bundledPlugins = lib.filter (p: p != null) (lib.mapAttrsToList (name: source: + bundledPluginSources = let - pluginCfg = cfg.bundledPlugins.${name}; + stepieteRev = "983210e3b6e9285780e87f48ce9354b51a270e95"; + stepieteNarHash = "sha256-fY8t41kMSHu2ovf89mIdvC7vkceroCwKxw/MKVn4rsE="; + stepiete = + tool: + "github:openclaw/nix-steipete-tools?dir=tools/${tool}&rev=${stepieteRev}&narHash=${stepieteNarHash}"; in - if (pluginCfg.enable or false) then { - inherit source; - config = pluginCfg.config or {}; - } else null - ) bundledPluginSources); + { + summarize = stepiete "summarize"; + peekaboo = stepiete "peekaboo"; + oracle = stepiete "oracle"; + poltergeist = stepiete "poltergeist"; + sag = stepiete "sag"; + camsnap = stepiete "camsnap"; + gogcli = stepiete "gogcli"; + goplaces = stepiete "goplaces"; + bird = stepiete "bird"; + sonoscli = stepiete "sonoscli"; + imsg = stepiete "imsg"; + }; + + bundledPlugins = lib.filter (p: p != null) ( + lib.mapAttrsToList ( + name: source: + let + pluginCfg = cfg.bundledPlugins.${name}; + in + if (pluginCfg.enable or false) then + { + inherit source; + config = pluginCfg.config or { }; + } + else + null + ) bundledPluginSources + ); effectivePlugins = cfg.customPlugins ++ bundledPlugins; - resolvePath = p: - if lib.hasPrefix "~/" p then - "${homeDir}/${lib.removePrefix "~/" p}" - else - p; + resolvePath = p: if lib.hasPrefix "~/" p then "${homeDir}/${lib.removePrefix "~/" p}" else p; - toRelative = p: - if lib.hasPrefix "${homeDir}/" p then - lib.removePrefix "${homeDir}/" p - else - p; + toRelative = p: if lib.hasPrefix "${homeDir}/" p then lib.removePrefix "${homeDir}/" p else p; -in { +in +{ inherit cfg homeDir @@ -75,5 +82,6 @@ in { bundledPlugins effectivePlugins resolvePath - toRelative; + toRelative + ; } diff --git a/nix/modules/home-manager/openclaw/options-instance.nix b/nix/modules/home-manager/openclaw/options-instance.nix index cbd475d..2e169bf 100644 --- a/nix/modules/home-manager/openclaw/options-instance.nix +++ b/nix/modules/home-manager/openclaw/options-instance.nix @@ -17,9 +17,11 @@ stateDir = lib.mkOption { type = lib.types.str; - default = if name == "default" - then "${openclawLib.homeDir}/.openclaw" - else "${openclawLib.homeDir}/.openclaw-${name}"; + default = + if name == "default" then + "${openclawLib.homeDir}/.openclaw" + else + "${openclawLib.homeDir}/.openclaw-${name}"; description = "State directory for this OpenClaw instance (logs, sessions, config)."; }; @@ -37,9 +39,11 @@ logPath = lib.mkOption { type = lib.types.str; - default = if name == "default" - then "/tmp/openclaw/openclaw-gateway.log" - else "/tmp/openclaw/openclaw-gateway-${name}.log"; + default = + if name == "default" then + "/tmp/openclaw/openclaw-gateway.log" + else + "/tmp/openclaw/openclaw-gateway-${name}.log"; description = "Log path for this OpenClaw gateway instance."; }; @@ -62,26 +66,28 @@ }; plugins = lib.mkOption { - type = lib.types.listOf (lib.types.submodule { - options = { - source = lib.mkOption { - type = lib.types.str; - description = "Plugin source pointer (e.g., github:owner/repo or path:/...)."; + type = lib.types.listOf ( + lib.types.submodule { + options = { + source = lib.mkOption { + type = lib.types.str; + description = "Plugin source pointer (e.g., github:owner/repo or path:/...)."; + }; + config = lib.mkOption { + type = lib.types.attrs; + default = { }; + description = "Plugin-specific configuration (env/files/etc)."; + }; }; - config = lib.mkOption { - type = lib.types.attrs; - default = {}; - description = "Plugin-specific configuration (env/files/etc)."; - }; - }; - }); + } + ); default = openclawLib.effectivePlugins; description = "Plugins enabled for this instance (includes first-party toggles)."; }; config = lib.mkOption { type = lib.types.submodule { options = openclawLib.generatedConfigOptions; }; - default = {}; + default = { }; description = "OpenClaw config (schema-typed)."; }; @@ -93,9 +99,11 @@ launchd.label = lib.mkOption { type = lib.types.str; - default = if name == "default" - then "com.steipete.openclaw.gateway" - else "com.steipete.openclaw.gateway.${name}"; + default = + if name == "default" then + "com.steipete.openclaw.gateway" + else + "com.steipete.openclaw.gateway.${name}"; description = "launchd label for this instance."; }; @@ -107,9 +115,7 @@ systemd.unitName = lib.mkOption { type = lib.types.str; - default = if name == "default" - then "openclaw-gateway" - else "openclaw-gateway-${name}"; + default = if name == "default" then "openclaw-gateway" else "openclaw-gateway-${name}"; description = "systemd user service unit name for this instance."; }; diff --git a/nix/modules/home-manager/openclaw/options.nix b/nix/modules/home-manager/openclaw/options.nix index d9e63d3..8515e72 100644 --- a/nix/modules/home-manager/openclaw/options.nix +++ b/nix/modules/home-manager/openclaw/options.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let openclawLib = import ./lib.nix { inherit config lib pkgs; }; @@ -30,7 +35,11 @@ let description = "Optional openclaw metadata for the skill frontmatter."; }; mode = lib.mkOption { - type = lib.types.enum [ "symlink" "copy" "inline" ]; + type = lib.types.enum [ + "symlink" + "copy" + "inline" + ]; default = "symlink"; description = "Install mode for the skill (symlink/copy/inline)."; }; @@ -42,7 +51,8 @@ let }; }; -in { +in +{ options.programs.openclaw = { enable = lib.mkEnableOption "OpenClaw (batteries-included)"; @@ -60,7 +70,7 @@ in { excludeTools = lib.mkOption { type = lib.types.listOf lib.types.str; - default = []; + default = [ ]; description = "Tool names to remove from the built-in toolchain."; }; @@ -104,54 +114,66 @@ in { skills = lib.mkOption { type = lib.types.listOf mkSkillOption; - default = []; + default = [ ]; description = "Declarative skills installed into each instance workspace."; }; customPlugins = lib.mkOption { - type = lib.types.listOf (lib.types.submodule { - options = { - source = lib.mkOption { - type = lib.types.str; - description = "Plugin source pointer (e.g., github:owner/repo or path:/...)."; + type = lib.types.listOf ( + lib.types.submodule { + options = { + source = lib.mkOption { + type = lib.types.str; + description = "Plugin source pointer (e.g., github:owner/repo or path:/...)."; + }; + config = lib.mkOption { + type = lib.types.attrs; + default = { }; + description = "Plugin-specific configuration (env/files/etc)."; + }; }; - config = lib.mkOption { - type = lib.types.attrs; - default = {}; - description = "Plugin-specific configuration (env/files/etc)."; - }; - }; - }); - default = []; + } + ); + default = [ ]; description = "Custom/community plugins (merged with bundled plugin toggles)."; }; - bundledPlugins = let - mkPlugin = { name, defaultEnable ? false }: { - enable = lib.mkOption { - type = lib.types.bool; - default = defaultEnable; - description = "Enable the ${name} plugin (bundled)."; - }; - config = lib.mkOption { - type = lib.types.attrs; - default = {}; - description = "Bundled plugin configuration passed through to ${name} (env/settings)."; + bundledPlugins = + let + mkPlugin = + { + name, + defaultEnable ? false, + }: + { + enable = lib.mkOption { + type = lib.types.bool; + default = defaultEnable; + description = "Enable the ${name} plugin (bundled)."; + }; + config = lib.mkOption { + type = lib.types.attrs; + default = { }; + description = "Bundled plugin configuration passed through to ${name} (env/settings)."; + }; + }; + in + { + summarize = mkPlugin { name = "summarize"; }; + peekaboo = mkPlugin { name = "peekaboo"; }; + oracle = mkPlugin { name = "oracle"; }; + poltergeist = mkPlugin { name = "poltergeist"; }; + sag = mkPlugin { name = "sag"; }; + camsnap = mkPlugin { name = "camsnap"; }; + gogcli = mkPlugin { name = "gogcli"; }; + goplaces = mkPlugin { + name = "goplaces"; + defaultEnable = true; }; + bird = mkPlugin { name = "bird"; }; + sonoscli = mkPlugin { name = "sonoscli"; }; + imsg = mkPlugin { name = "imsg"; }; }; - in { - summarize = mkPlugin { name = "summarize"; }; - peekaboo = mkPlugin { name = "peekaboo"; }; - oracle = mkPlugin { name = "oracle"; }; - poltergeist = mkPlugin { name = "poltergeist"; }; - sag = mkPlugin { name = "sag"; }; - camsnap = mkPlugin { name = "camsnap"; }; - gogcli = mkPlugin { name = "gogcli"; }; - goplaces = mkPlugin { name = "goplaces"; defaultEnable = true; }; - bird = mkPlugin { name = "bird"; }; - sonoscli = mkPlugin { name = "sonoscli"; }; - imsg = mkPlugin { name = "imsg"; }; - }; launchd.enable = lib.mkOption { type = lib.types.bool; @@ -179,7 +201,7 @@ in { instances = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule instanceModule); - default = {}; + default = { }; description = "Named OpenClaw instances (prod/test)."; }; @@ -199,7 +221,7 @@ in { config = lib.mkOption { type = lib.types.submodule { options = openclawLib.generatedConfigOptions; }; - default = {}; + default = { }; description = "OpenClaw config (schema-typed)."; }; }; diff --git a/nix/modules/home-manager/openclaw/plugins.nix b/nix/modules/home-manager/openclaw/plugins.nix index 12d70f5..6d968d8 100644 --- a/nix/modules/home-manager/openclaw/plugins.nix +++ b/nix/modules/home-manager/openclaw/plugins.nix @@ -1,104 +1,124 @@ -{ lib, pkgs, openclawLib, enabledInstances }: +{ + lib, + pkgs, + openclawLib, + enabledInstances, +}: let resolvePath = openclawLib.resolvePath; toRelative = openclawLib.toRelative; - resolvePlugin = plugin: let - flake = builtins.getFlake plugin.source; - system = pkgs.stdenv.hostPlatform.system; - openclawPluginRaw = - if flake ? openclawPlugin then flake.openclawPlugin - else throw "openclawPlugin missing in ${plugin.source}"; - openclawPlugin = - if builtins.isFunction openclawPluginRaw - then openclawPluginRaw system - else openclawPluginRaw; - resolvedPlugin = - if openclawPlugin == null - then throw "openclawPlugin is null in ${plugin.source} for ${system}" - else openclawPlugin; - needs = resolvedPlugin.needs or {}; - in { - source = plugin.source; - name = resolvedPlugin.name or (throw "openclawPlugin.name missing in ${plugin.source}"); - skills = resolvedPlugin.skills or []; - packages = resolvedPlugin.packages or []; - needs = { - stateDirs = needs.stateDirs or []; - requiredEnv = needs.requiredEnv or []; - }; - config = plugin.config or {}; - }; - - resolvedPluginsByInstance = - lib.mapAttrs (instName: inst: - let - resolved = map resolvePlugin inst.plugins; - counts = lib.foldl' (acc: p: - acc // { "${p.name}" = (acc.${p.name} or 0) + 1; } - ) {} resolved; - duplicates = lib.attrNames (lib.filterAttrs (_: v: v > 1) counts); - byName = lib.foldl' (acc: p: acc // { "${p.name}" = p; }) {} resolved; - ordered = lib.attrValues byName; - in - if duplicates == [] - then ordered - else lib.warn - "programs.openclaw.instances.${instName}: duplicate plugin names detected (${lib.concatStringsSep ", " duplicates}); last entry wins." - ordered - ) enabledInstances; - - pluginPackagesFor = instName: - lib.flatten (map (p: p.packages) (resolvedPluginsByInstance.${instName} or [])); - - pluginPackagesAll = - lib.flatten (map pluginPackagesFor (lib.attrNames enabledInstances)); - - pluginStateDirsFor = instName: + resolvePlugin = + plugin: let - dirs = lib.flatten (map (p: p.needs.stateDirs) (resolvedPluginsByInstance.${instName} or [])); + flake = builtins.getFlake plugin.source; + system = pkgs.stdenv.hostPlatform.system; + openclawPluginRaw = + if flake ? openclawPlugin then + flake.openclawPlugin + else + throw "openclawPlugin missing in ${plugin.source}"; + openclawPlugin = + if builtins.isFunction openclawPluginRaw then openclawPluginRaw system else openclawPluginRaw; + resolvedPlugin = + if openclawPlugin == null then + throw "openclawPlugin is null in ${plugin.source} for ${system}" + else + openclawPlugin; + needs = resolvedPlugin.needs or { }; in - map (dir: resolvePath ("~/" + dir)) dirs; + { + source = plugin.source; + name = resolvedPlugin.name or (throw "openclawPlugin.name missing in ${plugin.source}"); + skills = resolvedPlugin.skills or [ ]; + packages = resolvedPlugin.packages or [ ]; + needs = { + stateDirs = needs.stateDirs or [ ]; + requiredEnv = needs.requiredEnv or [ ]; + }; + config = plugin.config or { }; + }; - pluginStateDirsAll = - lib.flatten (map pluginStateDirsFor (lib.attrNames enabledInstances)); - - pluginEnvFor = instName: + resolvedPluginsByInstance = lib.mapAttrs ( + instName: inst: let - entries = resolvedPluginsByInstance.${instName} or []; - toPairs = p: + resolved = map resolvePlugin inst.plugins; + counts = lib.foldl' (acc: p: acc // { "${p.name}" = (acc.${p.name} or 0) + 1; }) { } resolved; + duplicates = lib.attrNames (lib.filterAttrs (_: v: v > 1) counts); + byName = lib.foldl' (acc: p: acc // { "${p.name}" = p; }) { } resolved; + ordered = lib.attrValues byName; + in + if duplicates == [ ] then + ordered + else + lib.warn "programs.openclaw.instances.${instName}: duplicate plugin names detected (${lib.concatStringsSep ", " duplicates}); last entry wins." ordered + ) enabledInstances; + + pluginPackagesFor = + instName: lib.flatten (map (p: p.packages) (resolvedPluginsByInstance.${instName} or [ ])); + + pluginPackagesAll = lib.flatten (map pluginPackagesFor (lib.attrNames enabledInstances)); + + pluginStateDirsFor = + instName: + let + dirs = lib.flatten (map (p: p.needs.stateDirs) (resolvedPluginsByInstance.${instName} or [ ])); + in + map (dir: resolvePath ("~/" + dir)) dirs; + + pluginStateDirsAll = lib.flatten (map pluginStateDirsFor (lib.attrNames enabledInstances)); + + pluginEnvFor = + instName: + let + entries = resolvedPluginsByInstance.${instName} or [ ]; + toPairs = + p: let - env = (p.config.env or {}); + env = (p.config.env or { }); required = p.needs.requiredEnv; in - map (k: { key = k; value = env.${k} or ""; plugin = p.name; }) required; + map (k: { + key = k; + value = env.${k} or ""; + plugin = p.name; + }) required; in - lib.flatten (map toPairs entries); + lib.flatten (map toPairs entries); - pluginEnvAllFor = instName: + pluginEnvAllFor = + instName: let - entries = resolvedPluginsByInstance.${instName} or []; - toPairs = p: - let env = (p.config.env or {}); - in map (k: { key = k; value = env.${k}; plugin = p.name; }) (lib.attrNames env); + entries = resolvedPluginsByInstance.${instName} or [ ]; + toPairs = + p: + let + env = (p.config.env or { }); + in + map (k: { + key = k; + value = env.${k}; + plugin = p.name; + }) (lib.attrNames env); in - lib.flatten (map toPairs entries); + lib.flatten (map toPairs entries); - pluginAssertions = - lib.flatten (lib.mapAttrsToList (instName: inst: + pluginAssertions = lib.flatten ( + lib.mapAttrsToList ( + instName: inst: let - plugins = resolvedPluginsByInstance.${instName} or []; - envFor = p: (p.config.env or {}); - missingFor = p: - lib.filter (req: !(builtins.hasAttr req (envFor p))) p.needs.requiredEnv; - configMissingStateDir = p: - (p.config.settings or {}) != {} && (p.needs.stateDirs or []) == []; - mkAssertion = p: + plugins = resolvedPluginsByInstance.${instName} or [ ]; + envFor = p: (p.config.env or { }); + missingFor = p: lib.filter (req: !(builtins.hasAttr req (envFor p))) p.needs.requiredEnv; + configMissingStateDir = p: (p.config.settings or { }) != { } && (p.needs.stateDirs or [ ]) == [ ]; + mkAssertion = + p: let missing = missingFor p; - in { - assertion = missing == []; + in + { + assertion = missing == [ ]; message = "programs.openclaw.instances.${instName}: plugin ${p.name} missing required env: ${lib.concatStringsSep ", " missing}"; }; mkConfigAssertion = p: { @@ -106,75 +126,86 @@ let message = "programs.openclaw.instances.${instName}: plugin ${p.name} provides settings but declares no stateDirs (needed for config.json)."; }; in - (map mkAssertion plugins) ++ (map mkConfigAssertion plugins) - ) enabledInstances); + (map mkAssertion plugins) ++ (map mkConfigAssertion plugins) + ) enabledInstances + ); pluginSkillsFiles = let - entriesForInstance = instName: inst: + entriesForInstance = + instName: inst: let base = "${toRelative (resolvePath inst.workspaceDir)}/skills"; - skillEntriesFor = p: + skillEntriesFor = + p: map (skillPath: { name = "${base}/${builtins.baseNameOf skillPath}"; - value = { source = skillPath; recursive = true; }; + value = { + source = skillPath; + recursive = true; + }; }) p.skills; - plugins = resolvedPluginsByInstance.${instName} or []; + plugins = resolvedPluginsByInstance.${instName} or [ ]; in - lib.flatten (map skillEntriesFor plugins); + lib.flatten (map skillEntriesFor plugins); in - lib.listToAttrs (lib.flatten (lib.mapAttrsToList entriesForInstance enabledInstances)); + lib.listToAttrs (lib.flatten (lib.mapAttrsToList entriesForInstance enabledInstances)); pluginConfigFiles = let - entryFor = instName: inst: + entryFor = + instName: inst: let - plugins = resolvedPluginsByInstance.${instName} or []; - mkEntries = p: + plugins = resolvedPluginsByInstance.${instName} or [ ]; + mkEntries = + p: let - cfg = p.config.settings or {}; - dir = - if (p.needs.stateDirs or []) == [] - then null - else lib.head (p.needs.stateDirs or []); + cfg = p.config.settings or { }; + dir = if (p.needs.stateDirs or [ ]) == [ ] then null else lib.head (p.needs.stateDirs or [ ]); in - if cfg == {} then - [] - else - (if dir == null then + if cfg == { } then + [ ] + else + ( + if dir == null then throw "plugin ${p.name} provides settings but no stateDirs are defined" - else [ - { - name = toRelative (resolvePath ("~/" + dir + "/config.json")); - value = { text = builtins.toJSON cfg; }; - } - ]); + else + [ + { + name = toRelative (resolvePath ("~/" + dir + "/config.json")); + value = { + text = builtins.toJSON cfg; + }; + } + ] + ); in - lib.flatten (map mkEntries plugins); + lib.flatten (map mkEntries plugins); entries = lib.flatten (lib.mapAttrsToList entryFor enabledInstances); in - lib.listToAttrs entries; + lib.listToAttrs entries; pluginSkillAssertions = let - skillTargets = - lib.flatten (lib.concatLists (lib.mapAttrsToList (instName: inst: - let - base = "${toRelative (resolvePath inst.workspaceDir)}/skills"; - plugins = resolvedPluginsByInstance.${instName} or []; - in - map (p: - map (skillPath: - "${base}/${p.name}/${builtins.baseNameOf skillPath}" - ) p.skills - ) plugins - ) enabledInstances)); - counts = lib.foldl' (acc: path: - acc // { "${path}" = (acc.${path} or 0) + 1; } - ) {} skillTargets; + skillTargets = lib.flatten ( + lib.concatLists ( + lib.mapAttrsToList ( + instName: inst: + let + base = "${toRelative (resolvePath inst.workspaceDir)}/skills"; + plugins = resolvedPluginsByInstance.${instName} or [ ]; + in + map (p: map (skillPath: "${base}/${p.name}/${builtins.baseNameOf skillPath}") p.skills) plugins + ) enabledInstances + ) + ); + counts = lib.foldl' (acc: path: acc // { "${path}" = (acc.${path} or 0) + 1; }) { } skillTargets; duplicates = lib.attrNames (lib.filterAttrs (_: v: v > 1) counts); in - if duplicates == [] then [] else [ + if duplicates == [ ] then + [ ] + else + [ { assertion = false; message = "Duplicate skill paths detected: ${lib.concatStringsSep ", " duplicates}"; @@ -193,13 +224,14 @@ let exit 1 fi ''; - entriesForInstance = instName: - map (entry: entry // { instance = instName; }) (pluginEnvFor instName); + entriesForInstance = + instName: map (entry: entry // { instance = instName; }) (pluginEnvFor instName); entries = lib.flatten (map entriesForInstance (lib.attrNames enabledInstances)); in - lib.concatStringsSep "\n" (map renderCheck entries); + lib.concatStringsSep "\n" (map renderCheck entries); -in { +in +{ inherit resolvedPluginsByInstance pluginPackagesFor @@ -212,5 +244,6 @@ in { pluginSkillsFiles pluginConfigFiles pluginSkillAssertions - pluginGuards; + pluginGuards + ; } diff --git a/nix/overlay.nix b/nix/overlay.nix index a9e9c57..79a82b5 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -2,13 +2,18 @@ final: prev: let packages = import ./packages { pkgs = prev; }; toolNames = (import ./tools/extended.nix { pkgs = prev; }).toolNames; - withTools = { toolNamesOverride ? null, excludeToolNames ? [] }: + withTools = + { + toolNamesOverride ? null, + excludeToolNames ? [ ], + }: import ./packages { pkgs = prev; inherit toolNamesOverride excludeToolNames; }; in -packages // { +packages +// { openclawPackages = packages // { inherit toolNames withTools; }; diff --git a/nix/packages/default.nix b/nix/packages/default.nix index fde5ac5..3f39531 100644 --- a/nix/packages/default.nix +++ b/nix/packages/default.nix @@ -1,8 +1,9 @@ -{ pkgs -, sourceInfo ? import ../sources/openclaw-source.nix -, steipetePkgs ? {} -, toolNamesOverride ? null -, excludeToolNames ? [] +{ + pkgs, + sourceInfo ? import ../sources/openclaw-source.nix, + steipetePkgs ? { }, + toolNamesOverride ? null, + excludeToolNames ? [ ], }: let isDarwin = pkgs.stdenv.hostPlatform.isDarwin; @@ -26,8 +27,10 @@ let openclaw-app = openclawApp; extendedTools = toolSets.tools; }; -in { +in +{ openclaw-gateway = openclawGateway; openclaw = openclawBundle; openclaw-tools = openclawTools; -} // (if isDarwin then { openclaw-app = openclawApp; } else {}) +} +// (if isDarwin then { openclaw-app = openclawApp; } else { }) diff --git a/nix/packages/openclaw-app.nix b/nix/packages/openclaw-app.nix index 7fef145..b89aa56 100644 --- a/nix/packages/openclaw-app.nix +++ b/nix/packages/openclaw-app.nix @@ -1,6 +1,7 @@ -{ lib -, stdenvNoCC -, fetchzip +{ + lib, + stdenvNoCC, + fetchzip, }: stdenvNoCC.mkDerivation { diff --git a/nix/packages/openclaw-batteries.nix b/nix/packages/openclaw-batteries.nix index c42bbfb..08b0f77 100644 --- a/nix/packages/openclaw-batteries.nix +++ b/nix/packages/openclaw-batteries.nix @@ -1,8 +1,9 @@ -{ lib -, buildEnv -, openclaw-gateway -, openclaw-app ? null -, extendedTools ? [] +{ + lib, + buildEnv, + openclaw-gateway, + openclaw-app ? null, + extendedTools ? [ ], }: let diff --git a/nix/tests/hm-activation-macos/flake.nix b/nix/tests/hm-activation-macos/flake.nix index 44a3bf7..7722092 100644 --- a/nix/tests/hm-activation-macos/flake.nix +++ b/nix/tests/hm-activation-macos/flake.nix @@ -8,45 +8,55 @@ nix-openclaw.url = "github:openclaw/nix-openclaw"; }; - outputs = { nixpkgs, home-manager, nix-openclaw, ... }: + outputs = + { + nixpkgs, + home-manager, + nix-openclaw, + ... + }: let system = "aarch64-darwin"; pkgs = import nixpkgs { inherit system; overlays = [ nix-openclaw.overlays.default ]; }; - in { + in + { homeConfigurations.hm-test = home-manager.lib.homeManagerConfiguration { inherit pkgs; modules = [ nix-openclaw.homeManagerModules.openclaw - ({ ... }: { - home = { - username = "runner"; - homeDirectory = "/tmp/hm-activation-home"; - stateVersion = "23.11"; - }; + ( + { ... }: + { + home = { + username = "runner"; + homeDirectory = "/tmp/hm-activation-home"; + stateVersion = "23.11"; + }; - programs.openclaw = { - enable = true; - installApp = false; - instances.default = { - gatewayPort = 18999; - config = { - logging = { - level = "debug"; - file = "/tmp/openclaw/openclaw-gateway.log"; - }; - gateway = { - mode = "local"; - auth = { - token = "hm-activation-test-token"; + programs.openclaw = { + enable = true; + installApp = false; + instances.default = { + gatewayPort = 18999; + config = { + logging = { + level = "debug"; + file = "/tmp/openclaw/openclaw-gateway.log"; + }; + gateway = { + mode = "local"; + auth = { + token = "hm-activation-test-token"; + }; }; }; }; }; - }; - }) + } + ) ]; }; }; diff --git a/nix/tools/extended.nix b/nix/tools/extended.nix index 007f529..f45038c 100644 --- a/nix/tools/extended.nix +++ b/nix/tools/extended.nix @@ -1,19 +1,26 @@ -{ pkgs -, steipetePkgs ? {} -, toolNamesOverride ? null -, excludeToolNames ? [] +{ + pkgs, + steipetePkgs ? { }, + toolNamesOverride ? null, + excludeToolNames ? [ ], }: let lib = pkgs.lib; safe = list: builtins.filter (p: p != null) list; - pickFrom = scope: name: + pickFrom = + scope: name: if builtins.hasAttr name scope then - let pkg = scope.${name}; in + let + pkg = scope.${name}; + in if lib.meta.availableOn pkgs.stdenv.hostPlatform pkg then pkg else null else null; - pick = name: - let fromSteipete = pickFrom steipetePkgs name; in + pick = + name: + let + fromSteipete = pickFrom steipetePkgs name; + in if fromSteipete != null then fromSteipete else pickFrom pkgs name; ensure = names: safe (map pick names); @@ -55,7 +62,8 @@ let toolNamesBase = if toolNamesOverride != null then toolNamesOverride else baseNames ++ extraNames; toolNames = builtins.filter (name: !builtins.elem name excludeToolNames) toolNamesBase; -in { +in +{ tools = ensure toolNames; toolNames = toolNames; } diff --git a/templates/agent-first/flake.nix b/templates/agent-first/flake.nix index 4dd0455..2c37dc1 100644 --- a/templates/agent-first/flake.nix +++ b/templates/agent-first/flake.nix @@ -8,12 +8,22 @@ nix-openclaw.url = "github:openclaw/nix-openclaw"; }; - outputs = { self, nixpkgs, home-manager, nix-openclaw }: + outputs = + { + self, + nixpkgs, + home-manager, + nix-openclaw, + }: let # REPLACE: aarch64-darwin (Apple Silicon), x86_64-darwin (Intel), or x86_64-linux system = ""; - pkgs = import nixpkgs { inherit system; overlays = [ nix-openclaw.overlays.default ]; }; - in { + pkgs = import nixpkgs { + inherit system; + overlays = [ nix-openclaw.overlays.default ]; + }; + in + { # REPLACE: with your username (run `whoami`) homeConfigurations."" = home-manager.lib.homeManagerConfiguration { inherit pkgs; @@ -47,7 +57,9 @@ # REPLACE: your Telegram user ID (get from @userinfobot) allowFrom = [ ]; groups = { - "*" = { requireMention = true; }; + "*" = { + requireMention = true; + }; }; }; };