refactor: remove dead code, extract shared agent options, optimize flake

- Remove dead overlays/default.nix (flake defines overlays inline)
- Remove orphaned overlays/mods/{beads,n8n}.nix (never imported)
- Remove docs/packages/notesmd-cli.md (package doesn't exist)
- Extract externalSkills submodule to shared-options.nix (eliminates
  ~100 lines of duplication across opencode/claude-code/pi modules)
- Fix lib output: use nixpkgs.lib directly instead of instantiating
  a full nixpkgs just to get lib
- Add lib unit tests to flake checks
- Update stale comment in coding-rules.nix
This commit is contained in:
Chiron
2026-04-18 10:15:50 +00:00
parent c9ecc0809f
commit 54fa93574b
10 changed files with 408 additions and 661 deletions

View File

@@ -1,117 +0,0 @@
# notesmd-cli
Obsidian CLI (Community) - Interact with Obsidian in the terminal.
## Description
notesmd-cli is a command-line interface for interacting with Obsidian, the popular knowledge management and note-taking application. It allows you to create, search, and manipulate notes directly from the terminal.
## Features
- 📝 **Note Creation**: Create new notes from the command line
- 🔍 **Search**: Search through your Obsidian vault
- 📂 **Vault Management**: Interact with your vault structure
- 🔗 **WikiLink Support**: Work with Obsidian's WikiLink format
- 🏷️ **Tag Support**: Manage and search by tags
-**Fast**: Lightweight Go binary with no external dependencies
## Installation
### Via Overlay
```nix
{pkgs, ...}: {
environment.systemPackages = with pkgs; [
notesmd-cli
];
}
```
### Direct Reference
```nix
{pkgs, ...}: {
environment.systemPackages = with pkgs; [
inputs.m3ta-nixpkgs.packages.${pkgs.system}.notesmd-cli
];
}
```
### Run Directly
```bash
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#notesmd-cli
```
## Usage
### Basic Commands
```bash
# Show help
notesmd-cli --help
# Create a new note
notesmd-cli new "My Note Title"
# Search notes
notesmd-cli search "search term"
# List notes
notesmd-cli list
```
### Working with Vaults
```bash
# Specify vault path
notesmd-cli --vault /path/to/vault new "Note Title"
# Open a note in Obsidian
notesmd-cli open "Note Name"
```
### Advanced Usage
```bash
# Search with tags
notesmd-cli search --tag "project"
# Append to existing note
notesmd-cli append "Note Name" "Additional content"
```
## Configuration
### Environment Variables
- `OBSIDIAN_VAULT`: Default vault path
### Command Line Options
Run `notesmd-cli --help` for a complete list of options.
## Build Information
- **Version**: 0.3.0
- **Language**: Go
- **License**: MIT
- **Source**: [GitHub](https://github.com/Yakitrak/notesmd-cli)
- **Vendor Hash**: null (no external dependencies)
## Platform Support
- Linux
- macOS (Unix systems)
## Notes
- No vendor dependencies (pure Go stdlib)
- The binary is named `notesmd-cli` (not `notesmd`)
- This is the community CLI, not the official Obsidian CLI
## Related
- [Obsidian](https://obsidian.md) - The Obsidian application
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
- [Quick Start](../QUICKSTART.md) - Getting started guide

View File

@@ -77,10 +77,7 @@
}; };
# Library functions - helper utilities for your configuration # Library functions - helper utilities for your configuration
lib = forAllSystems (system: let lib = forAllSystems (system: import ./lib {lib = nixpkgs.lib;});
pkgs = pkgsFor system;
in
import ./lib {lib = pkgs.lib;});
# Development shells for various programming environments # Development shells for various programming environments
# Usage: nix develop .#<shell-name> # Usage: nix develop .#<shell-name>
@@ -104,6 +101,9 @@
${pkgs.alejandra}/bin/alejandra --check ${./.} ${pkgs.alejandra}/bin/alejandra --check ${./.}
touch $out touch $out
''; '';
# Lib unit tests
lib-agents = import ./tests/lib/agents-test.nix;
lib-coding-rules = import ./tests/lib/coding-rules-test.nix;
}); });
# Templates for creating new packages/modules # Templates for creating new packages/modules

View File

@@ -1,4 +1,4 @@
# Opencode rules management utilities # Coding rules management utilities
# #
# This module provides functions to configure Opencode agent rules across # This module provides functions to configure Opencode agent rules across
# multiple projects. Rules are defined in the AGENTS repository and can be # multiple projects. Rules are defined in the AGENTS repository and can be

View File

@@ -3,144 +3,87 @@
lib, lib,
pkgs, pkgs,
... ...
}: }: let
with lib; let shared = import ./shared-options.nix {inherit lib;};
cfg = config.coding.agents.claude-code; in
mcpCfg = config.programs.mcp or null; with lib; let
in { cfg = config.coding.agents.claude-code;
options.coding.agents.claude-code = { mcpCfg = config.programs.mcp or null;
enable = mkEnableOption "Claude Code agent management via canonical agent.toml definitions"; in {
options.coding.agents.claude-code = {
enable = mkEnableOption "Claude Code agent management via canonical agent.toml definitions";
agentsInput = mkOption { agentsInput = shared.mkAgentsInputOption ''
type = types.nullOr types.anything;
default = null;
description = ''
The `agents` flake input (your personal AGENTS repo). The `agents` flake input (your personal AGENTS repo).
When set, agents are rendered from canonical agent.toml files When set, agents are rendered from canonical agent.toml files
and symlinked to ~/.claude/agents/. and symlinked to ~/.claude/agents/.
''; '';
};
modelOverrides = mkOption { modelOverrides = shared.mkModelOverridesOption;
type = types.attrsOf types.str;
default = {};
description = ''
Per-agent model overrides. Maps agent slug to model alias or ID.
Example: { chiron = "claude-sonnet-4-20250514"; }
'';
example = literalExpression ''
{
chiron = "claude-sonnet-4-20250514";
"chiron-forge" = "claude-sonnet-4-20250514";
}
'';
};
externalSkills = mkOption { externalSkills = shared.externalSkillsOption;
type = types.listOf (types.submodule {
options = {
src = mkOption {
type = types.anything;
description = "Flake input pointing to a skills repository root.";
};
skillsDir = mkOption {
type = types.str;
default = "skills";
description = ''
Subdirectory inside src that contains skill folders.
'';
};
selectSkills = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
description = ''
List of skill names to cherry-pick from this source.
null means include every skill found in skillsDir.
'';
};
};
});
default = [];
description = ''
External skill sources passed to mkOpencodeSkills.
Each entry maps directly to an element of the externalSkills
list accepted by the AGENTS flake's lib.mkOpencodeSkills.
'';
example = literalExpression ''
[
{ src = inputs.skills-anthropic; selectSkills = [ "claude-api" ]; }
{ src = inputs.skills-vercel; }
]
'';
};
mcpServers = mkOption { mcpServers = mkOption {
type = types.attrsOf types.anything; type = types.attrsOf types.anything;
default = default =
if mcpCfg != null if mcpCfg != null
then mcpCfg.servers then mcpCfg.servers
else {}; else {};
defaultText = literalExpression "config.programs.mcp.servers"; defaultText = literalExpression "config.programs.mcp.servers";
description = '' description = ''
MCP server configurations for Claude Code. MCP server configurations for Claude Code.
Merged into ~/.claude/settings.json alongside permissions. Merged into ~/.claude/settings.json alongside permissions.
Automatically inherits from config.programs.mcp.servers. Automatically inherits from config.programs.mcp.servers.
''; '';
};
};
config = mkIf cfg.enable (let
agentsLib = (import ../../../../lib {inherit lib;}).agents;
# Rendered agents + permissions (only if agentsInput is set)
rendered = mkIf (cfg.agentsInput != null) (
agentsLib.renderForClaudeCode {
inherit pkgs;
canonical = cfg.agentsInput.lib.loadAgents;
modelOverrides = cfg.modelOverrides;
}
);
# Merge MCP servers into the rendered settings.json.
# The renderer produces { permissions: { allow, deny } }.
# We add mcpServers on top.
settingsJson =
if cfg.agentsInput != null
then let
renderedSettings = builtins.fromJSON (builtins.readFile "${rendered}/.claude/settings.json");
withMcp =
if cfg.mcpServers != {}
then renderedSettings // {mcpServers = cfg.mcpServers;}
else renderedSettings;
in
pkgs.writeText "claude-settings.json" (builtins.toJSON withMcp)
else if cfg.mcpServers != {}
then pkgs.writeText "claude-settings.json" (builtins.toJSON {mcpServers = cfg.mcpServers;})
else null;
in {
# Rendered agent files symlinked to ~/.claude/agents/
home.file.".claude/agents" = mkIf (cfg.agentsInput != null) {
source = "${rendered}/.claude/agents";
};
# Skills (merged from personal AGENTS repo + optional external skills)
home.file.".claude/skills" = mkIf (cfg.agentsInput != null) {
source = cfg.agentsInput.lib.mkOpencodeSkills {
inherit pkgs;
customSkills = "${cfg.agentsInput}/skills";
externalSkills =
map (
entry:
{inherit (entry) src skillsDir;}
// optionalAttrs (entry.selectSkills != null) {inherit (entry) selectSkills;}
)
cfg.externalSkills;
}; };
}; };
# Rendered settings.json with permissions + MCP servers config = mkIf cfg.enable (let
home.file.".claude/settings.json" = mkIf (settingsJson != null) { agentsLib = (import ../../../../lib {inherit lib;}).agents;
source = "${settingsJson}";
}; # Rendered agents + permissions (only if agentsInput is set)
}); rendered = mkIf (cfg.agentsInput != null) (
} agentsLib.renderForClaudeCode {
inherit pkgs;
canonical = cfg.agentsInput.lib.loadAgents;
modelOverrides = cfg.modelOverrides;
}
);
# Merge MCP servers into the rendered settings.json.
# The renderer produces { permissions: { allow, deny } }.
# We add mcpServers on top.
settingsJson =
if cfg.agentsInput != null
then let
renderedSettings = builtins.fromJSON (builtins.readFile "${rendered}/.claude/settings.json");
withMcp =
if cfg.mcpServers != {}
then renderedSettings // {mcpServers = cfg.mcpServers;}
else renderedSettings;
in
pkgs.writeText "claude-settings.json" (builtins.toJSON withMcp)
else if cfg.mcpServers != {}
then pkgs.writeText "claude-settings.json" (builtins.toJSON {mcpServers = cfg.mcpServers;})
else null;
in {
# Rendered agent files symlinked to ~/.claude/agents/
home.file.".claude/agents" = mkIf (cfg.agentsInput != null) {
source = "${rendered}/.claude/agents";
};
# Skills (merged from personal AGENTS repo + optional external skills)
home.file.".claude/skills" = mkIf (cfg.agentsInput != null) {
source = cfg.agentsInput.lib.mkOpencodeSkills {
inherit pkgs;
customSkills = "${cfg.agentsInput}/skills";
externalSkills = shared.mapExternalSkills cfg.externalSkills;
};
};
# Rendered settings.json with permissions + MCP servers
home.file.".claude/settings.json" = mkIf (settingsJson != null) {
source = "${settingsJson}";
};
});
}

View File

@@ -3,111 +3,67 @@
lib, lib,
pkgs, pkgs,
... ...
}: }: let
with lib; let shared = import ./shared-options.nix {inherit lib;};
cfg = config.coding.agents.opencode; in
in { with lib; {
options.coding.agents.opencode = { options.coding.agents.opencode = {
enable = mkEnableOption "OpenCode agent management via canonical agent.toml definitions"; enable = mkEnableOption "OpenCode agent management via canonical agent.toml definitions";
agentsInput = mkOption { agentsInput = shared.mkAgentsInputOption ''
type = types.nullOr types.anything;
default = null;
description = ''
The `agents` flake input (your personal AGENTS repo). The `agents` flake input (your personal AGENTS repo).
When set, agents are rendered from canonical agent.toml files When set, agents are rendered from canonical agent.toml files
and symlinked to ~/.config/opencode/agents/. and symlinked to ~/.config/opencode/agents/.
''; '';
modelOverrides = shared.mkModelOverridesOption;
externalSkills = shared.externalSkillsOption;
}; };
modelOverrides = mkOption { config = mkIf config.coding.agents.opencode.enable {
type = types.attrsOf types.str; # Rendered agent files symlinked to ~/.config/opencode/agents/
default = {}; xdg.configFile."opencode/agents" = let
description = '' cfg = config.coding.agents.opencode;
Per-agent model overrides. Maps agent slug to model string. in
Example: { chiron = "anthropic/claude-sonnet-4"; } mkIf (cfg.agentsInput != null) {
''; source = (import ../../../../lib {inherit lib;}).agents.renderForOpencode {
example = literalExpression '' inherit pkgs;
{ canonical = cfg.agentsInput.lib.loadAgents;
chiron = "anthropic/claude-sonnet-4"; modelOverrides = cfg.modelOverrides;
"chiron-forge" = "anthropic/claude-sonnet-4";
}
'';
};
externalSkills = mkOption {
type = types.listOf (types.submodule {
options = {
src = mkOption {
type = types.anything;
description = "Flake input pointing to a skills repository root.";
};
skillsDir = mkOption {
type = types.str;
default = "skills";
description = ''
Subdirectory inside src that contains skill folders.
'';
};
selectSkills = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
description = ''
List of skill names to cherry-pick from this source.
null means include every skill found in skillsDir.
'';
}; };
}; };
});
default = [];
description = ''
External skill sources passed to mkOpencodeSkills.
Each entry maps directly to an element of the externalSkills
list accepted by the AGENTS flake's lib.mkOpencodeSkills.
'';
example = literalExpression ''
[
{ src = inputs.skills-anthropic; selectSkills = [ "claude-api" ]; }
{ src = inputs.skills-vercel; }
]
'';
};
};
config = mkIf cfg.enable { # Skills (merged from personal AGENTS repo + optional external skills)
# Rendered agent files symlinked to ~/.config/opencode/agents/ xdg.configFile."opencode/skills" = let
xdg.configFile."opencode/agents" = mkIf (cfg.agentsInput != null) { cfg = config.coding.agents.opencode;
source = (import ../../../../lib {inherit lib;}).agents.renderForOpencode { in
inherit pkgs; mkIf (cfg.agentsInput != null) {
canonical = cfg.agentsInput.lib.loadAgents; source = cfg.agentsInput.lib.mkOpencodeSkills {
modelOverrides = cfg.modelOverrides; inherit pkgs;
}; customSkills = "${cfg.agentsInput}/skills";
}; externalSkills = shared.mapExternalSkills cfg.externalSkills;
};
};
# Skills (merged from personal AGENTS repo + optional external skills) # Static config dirs from AGENTS repo
xdg.configFile."opencode/skills" = mkIf (cfg.agentsInput != null) { xdg.configFile."opencode/context" = let
source = cfg.agentsInput.lib.mkOpencodeSkills { cfg = config.coding.agents.opencode;
inherit pkgs; in
customSkills = "${cfg.agentsInput}/skills"; mkIf (cfg.agentsInput != null) {
externalSkills = source = "${cfg.agentsInput}/context";
map ( };
entry: xdg.configFile."opencode/commands" = let
{inherit (entry) src skillsDir;} cfg = config.coding.agents.opencode;
// optionalAttrs (entry.selectSkills != null) {inherit (entry) selectSkills;} in
) mkIf (cfg.agentsInput != null) {
cfg.externalSkills; source = "${cfg.agentsInput}/commands";
}; };
xdg.configFile."opencode/prompts" = let
cfg = config.coding.agents.opencode;
in
mkIf (cfg.agentsInput != null) {
source = "${cfg.agentsInput}/prompts";
};
}; };
}
# Static config dirs from AGENTS repo
xdg.configFile."opencode/context" = mkIf (cfg.agentsInput != null) {
source = "${cfg.agentsInput}/context";
};
xdg.configFile."opencode/commands" = mkIf (cfg.agentsInput != null) {
source = "${cfg.agentsInput}/commands";
};
xdg.configFile."opencode/prompts" = mkIf (cfg.agentsInput != null) {
source = "${cfg.agentsInput}/prompts";
};
};
}

View File

@@ -3,277 +3,225 @@
lib, lib,
pkgs, pkgs,
... ...
}: }: let
with lib; let shared = import ./shared-options.nix {inherit lib;};
cfg = config.coding.agents.pi; in
mcpCfg = config.programs.mcp or null; with lib; let
in { cfg = config.coding.agents.pi;
options.coding.agents.pi = { mcpCfg = config.programs.mcp or null;
enable = mkEnableOption "Pi agent management via canonical agent.toml definitions"; in {
options.coding.agents.pi = {
enable = mkEnableOption "Pi agent management via canonical agent.toml definitions";
mcpServers = mkOption { mcpServers = mkOption {
type = types.attrsOf types.anything; type = types.attrsOf types.anything;
default = if mcpCfg != null then mcpCfg.servers else {}; default = if mcpCfg != null then mcpCfg.servers else {};
defaultText = literalExpression "config.programs.mcp.servers"; defaultText = literalExpression "config.programs.mcp.servers";
description = '' description = ''
MCP server configurations for Pi (pi-mcp-adapter). MCP server configurations for Pi (pi-mcp-adapter).
Written to ~/.pi/agent/mcp.json. Written to ~/.pi/agent/mcp.json.
Automatically inherits from config.programs.mcp.servers. Automatically inherits from config.programs.mcp.servers.
''; '';
}; };
agentsInput = mkOption { agentsInput = shared.mkAgentsInputOption ''
type = types.nullOr types.anything;
default = null;
description = ''
The `agents` flake input (your personal AGENTS repo). The `agents` flake input (your personal AGENTS repo).
When set, the primary agent's system prompt is rendered as SYSTEM.md, When set, the primary agent's system prompt is rendered as SYSTEM.md,
all agents are listed in AGENTS.md, and subagent .md files are deployed. all agents are listed in AGENTS.md, and subagent .md files are deployed.
''; '';
};
modelOverrides = mkOption { modelOverrides = shared.mkModelOverridesOption;
type = types.attrsOf types.str;
default = {};
description = ''
Per-agent model overrides for Pi subagents.
Maps agent slug to model string, e.g.:
{ chiron = "anthropic/claude-sonnet-4"; chiron-forge = "anthropic/claude-sonnet-4"; }
'';
};
primaryAgent = mkOption { primaryAgent = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
description = '' description = ''
Override which canonical agent is used as primary for SYSTEM.md. Override which canonical agent is used as primary for SYSTEM.md.
When null, the first agent with mode="primary" is used. When null, the first agent with mode="primary" is used.
''; '';
};
externalSkills = mkOption {
type = types.listOf (types.submodule {
options = {
src = mkOption {
type = types.anything;
description = "Flake input pointing to a skills repository root.";
};
skillsDir = mkOption {
type = types.str;
default = "skills";
description = ''
Subdirectory inside src that contains skill folders.
'';
};
selectSkills = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
description = ''
List of skill names to cherry-pick from this source.
null means include every skill found in skillsDir.
'';
};
};
});
default = [];
description = ''
External skill sources passed to mkOpencodeSkills.
Each entry maps directly to an element of the externalSkills
list accepted by the AGENTS flake's lib.mkOpencodeSkills.
'';
example = literalExpression ''
[
{ src = inputs.skills-anthropic; selectSkills = [ "claude-api" ]; }
{ src = inputs.basecamp; }
]
'';
};
settings = mkOption {
type = types.submodule {
freeformType = types.attrsOf types.anything;
options = {
packages = mkOption {
type = types.listOf types.str;
default = [];
description = ''
Pi packages to install (npm:, git:, or local paths).
These are written to ~/.pi/agent/settings.json.
'';
};
defaultProvider = mkOption {
type = types.nullOr types.str;
default = null;
description = "Default LLM provider (e.g. 'anthropic', 'openai', 'zai').";
};
defaultModel = mkOption {
type = types.nullOr types.str;
default = null;
description = "Default model ID.";
};
defaultThinkingLevel = mkOption {
type = types.nullOr (types.enum ["off" "minimal" "low" "medium" "high" "xhigh"]);
default = null;
description = "Default extended thinking level.";
};
theme = mkOption {
type = types.nullOr types.str;
default = null;
description = "Pi theme name.";
};
hideThinkingBlock = mkOption {
type = types.nullOr types.bool;
default = null;
description = "Hide thinking blocks in output.";
};
quietStartup = mkOption {
type = types.nullOr types.bool;
default = null;
description = "Hide startup header.";
};
compaction = mkOption {
type = types.nullOr (types.submodule {
options = {
enabled = mkOption {
type = types.nullOr types.bool;
default = null;
};
reserveTokens = mkOption {
type = types.nullOr types.int;
default = null;
};
keepRecentTokens = mkOption {
type = types.nullOr types.int;
default = null;
};
};
});
default = null;
description = "Auto-compaction settings.";
};
enabledModels = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
description = "Model patterns for Ctrl+P cycling.";
};
sessionDir = mkOption {
type = types.nullOr types.str;
default = null;
description = "Directory where session files are stored.";
};
extensions = mkOption {
type = types.listOf types.str;
default = [];
description = "Local extension file paths or directories.";
};
skills = mkOption {
type = types.listOf types.str;
default = [];
description = "Local skill file paths or directories.";
};
};
}; };
default = {};
description = ''
Pi settings written to ~/.pi/agent/settings.json.
Only non-null values are included in the generated JSON.
See pi docs/settings.md for all options.
'';
};
};
config = mkIf cfg.enable (let externalSkills = shared.externalSkillsOption;
# Build settings.json by filtering out null values recursively
filterNulls = attrs:
lib.filterAttrs (_: v: v != null) (
builtins.mapAttrs (_: v:
if builtins.isAttrs v
then let
filtered = filterNulls v;
in
if filtered == {} then null else filtered
else v) attrs
);
piSettings = filterNulls cfg.settings; settings = mkOption {
type = types.submodule {
freeformType = types.attrsOf types.anything;
options = {
packages = mkOption {
type = types.listOf types.str;
default = [];
description = ''
Pi packages to install (npm:, git:, or local paths).
These are written to ~/.pi/agent/settings.json.
'';
};
# Rendered agents (only computed when agentsInput is set) defaultProvider = mkOption {
rendered = type = types.nullOr types.str;
if cfg.agentsInput != null default = null;
then description = "Default LLM provider (e.g. 'anthropic', 'openai', 'zai').";
(import ../../../../lib {inherit lib;}).agents.renderForPi { };
inherit pkgs;
canonical = cfg.agentsInput.lib.loadAgents;
modelOverrides = cfg.modelOverrides;
primaryAgent = cfg.primaryAgent;
}
else null;
# Dynamic home.file entries for agent .md files defaultModel = mkOption {
agentFiles = type = types.nullOr types.str;
if cfg.agentsInput != null default = null;
then description = "Default model ID.";
let };
agentNames = builtins.attrNames cfg.agentsInput.lib.loadAgents;
in
builtins.listToAttrs (
map (name: {
name = ".pi/agent/agents/${name}.md";
value = {source = "${rendered}/agents/${name}.md";};
})
agentNames
)
else {};
in {
home.file = mkMerge [
# ── MCP servers from programs.mcp → ~/.pi/agent/mcp.json ───────
(mkIf (cfg.mcpServers != {}) {
".pi/agent/mcp.json".text = builtins.toJSON {mcpServers = cfg.mcpServers;};
})
# ── ~/.pi/agent/settings.json ────────────────────────────────── defaultThinkingLevel = mkOption {
{ type = types.nullOr (types.enum ["off" "minimal" "low" "medium" "high" "xhigh"]);
".pi/agent/settings.json".text = builtins.toJSON piSettings; default = null;
} description = "Default extended thinking level.";
};
# ── AGENTS.md — agent descriptions and specialist listing ────── theme = mkOption {
(mkIf (cfg.agentsInput != null) { type = types.nullOr types.str;
".pi/agent/AGENTS.md".source = "${rendered}/AGENTS.md"; default = null;
}) description = "Pi theme name.";
};
# ── SYSTEM.md — primary agent's system prompt ────────────────── hideThinkingBlock = mkOption {
(mkIf (cfg.agentsInput != null) { type = types.nullOr types.bool;
".pi/agent/SYSTEM.md".source = "${rendered}/SYSTEM.md"; default = null;
}) description = "Hide thinking blocks in output.";
};
# ── Agents — pi-subagents .md files ──────────────────────────── quietStartup = mkOption {
agentFiles type = types.nullOr types.bool;
default = null;
description = "Hide startup header.";
};
# ── Skills symlinked from AGENTS repo + external skills ──────── compaction = mkOption {
(mkIf (cfg.agentsInput != null) { type = types.nullOr (types.submodule {
".pi/agent/skills".source = cfg.agentsInput.lib.mkOpencodeSkills { options = {
inherit pkgs; enabled = mkOption {
customSkills = "${cfg.agentsInput}/skills"; type = types.nullOr types.bool;
externalSkills = default = null;
map ( };
entry: reserveTokens = mkOption {
{inherit (entry) src skillsDir;} type = types.nullOr types.int;
// optionalAttrs (entry.selectSkills != null) {inherit (entry) selectSkills;} default = null;
) };
cfg.externalSkills; keepRecentTokens = mkOption {
type = types.nullOr types.int;
default = null;
};
};
});
default = null;
description = "Auto-compaction settings.";
};
enabledModels = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
description = "Model patterns for Ctrl+P cycling.";
};
sessionDir = mkOption {
type = types.nullOr types.str;
default = null;
description = "Directory where session files are stored.";
};
extensions = mkOption {
type = types.listOf types.str;
default = [];
description = "Local extension file paths or directories.";
};
skills = mkOption {
type = types.listOf types.str;
default = [];
description = "Local skill file paths or directories.";
};
};
}; };
}) default = {};
]; description = ''
}); Pi settings written to ~/.pi/agent/settings.json.
} Only non-null values are included in the generated JSON.
See pi docs/settings.md for all options.
'';
};
};
config = mkIf cfg.enable (let
# Build settings.json by filtering out null values recursively
filterNulls = attrs:
lib.filterAttrs (_: v: v != null) (
builtins.mapAttrs (_: v:
if builtins.isAttrs v
then let
filtered = filterNulls v;
in
if filtered == {} then null else filtered
else v) attrs
);
piSettings = filterNulls cfg.settings;
# Rendered agents (only computed when agentsInput is set)
rendered =
if cfg.agentsInput != null
then
(import ../../../../lib {inherit lib;}).agents.renderForPi {
inherit pkgs;
canonical = cfg.agentsInput.lib.loadAgents;
modelOverrides = cfg.modelOverrides;
primaryAgent = cfg.primaryAgent;
}
else null;
# Dynamic home.file entries for agent .md files
agentFiles =
if cfg.agentsInput != null
then
let
agentNames = builtins.attrNames cfg.agentsInput.lib.loadAgents;
in
builtins.listToAttrs (
map (name: {
name = ".pi/agent/agents/${name}.md";
value = {source = "${rendered}/agents/${name}.md";};
})
agentNames
)
else {};
in {
home.file = mkMerge [
# ── MCP servers from programs.mcp → ~/.pi/agent/mcp.json ───────
(mkIf (cfg.mcpServers != {}) {
".pi/agent/mcp.json".text = builtins.toJSON {mcpServers = cfg.mcpServers;};
})
# ── ~/.pi/agent/settings.json ──────────────────────────────────
{
".pi/agent/settings.json".text = builtins.toJSON piSettings;
}
# ── AGENTS.md — agent descriptions and specialist listing ──────
(mkIf (cfg.agentsInput != null) {
".pi/agent/AGENTS.md".source = "${rendered}/AGENTS.md";
})
# ── SYSTEM.md — primary agent's system prompt ──────────────────
(mkIf (cfg.agentsInput != null) {
".pi/agent/SYSTEM.md".source = "${rendered}/SYSTEM.md";
})
# ── Agents — pi-subagents .md files ────────────────────────────
agentFiles
# ── Skills symlinked from AGENTS repo + external skills ────────
(mkIf (cfg.agentsInput != null) {
".pi/agent/skills".source = cfg.agentsInput.lib.mkOpencodeSkills {
inherit pkgs;
customSkills = "${cfg.agentsInput}/skills";
externalSkills = shared.mapExternalSkills cfg.externalSkills;
};
})
];
});
}

View File

@@ -0,0 +1,75 @@
# Shared option definitions for agent modules.
# Prevents copy-pasting the externalSkills submodule across opencode/claude-code/pi.
{lib}: let
inherit (lib) mkOption mkEnableOption types literalExpression;
in {
# Common agentsInput option used by all agent modules.
mkAgentsInputOption = description: mkOption {
type = types.nullOr types.anything;
default = null;
inherit description;
};
# Common modelOverrides option.
mkModelOverridesOption = mkOption {
type = types.attrsOf types.str;
default = {};
description = ''
Per-agent model overrides. Maps agent slug to model string.
Example: { chiron = "anthropic/claude-sonnet-4"; }
'';
example = literalExpression ''
{
chiron = "anthropic/claude-sonnet-4";
"chiron-forge" = "anthropic/claude-sonnet-4";
}
'';
};
# External skills submodule — used by opencode, claude-code, and pi modules.
externalSkillsOption = mkOption {
type = types.listOf (types.submodule {
options = {
src = mkOption {
type = types.anything;
description = "Flake input pointing to a skills repository root.";
};
skillsDir = mkOption {
type = types.str;
default = "skills";
description = ''
Subdirectory inside src that contains skill folders.
'';
};
selectSkills = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
description = ''
List of skill names to cherry-pick from this source.
null means include every skill found in skillsDir.
'';
};
};
});
default = [];
description = ''
External skill sources passed to mkOpencodeSkills.
Each entry maps directly to an element of the externalSkills
list accepted by the AGENTS flake's lib.mkOpencodeSkills.
'';
example = literalExpression ''
[
{ src = inputs.skills-anthropic; selectSkills = [ "claude-api" ]; }
{ src = inputs.basecamp; }
]
'';
};
# Helper to map externalSkills from module config to mkOpencodeSkills format.
mapExternalSkills = cfgEntries:
map (
entry:
{inherit (entry) src skillsDir;}
// lib.optionalAttrs (entry.selectSkills != null) {inherit (entry) selectSkills;}
) cfgEntries;
}

View File

@@ -1,24 +0,0 @@
{inputs, ...}: {
# This one brings our custom packages from the 'pkgs' directory
additions = final: prev: (import ../pkgs {pkgs = final;});
# This one contains whatever you want to overlay
# You can change versions, add patches, set compilation flags, anything really.
# https://nixos.wiki/wiki/Overlays
modifications = final: prev:
# Import all package modifications from mods directory
(import ./mods/default.nix {inherit prev;})
// {
# Direct configuration overrides
brave = prev.brave.override {
commandLineArgs = "--password-store=gnome-libsecret";
};
};
master-packages = final: _prev: {
master = import inputs.nixpkgs-master {
system = final.stdenv.hostPlatform.system;
config.allowUnfree = true;
};
};
}

View File

@@ -1,16 +0,0 @@
{prev}:
prev.beads.overrideAttrs (oldAttrs: rec {
version = "0.47.1";
src = prev.fetchFromGitHub {
owner = "steveyegge";
repo = "beads";
tag = "v${version}";
hash = "sha256-DwIR/r1TJnpVd/CT1E2OTkAjU7k9/KHbcVwg5zziFVg=";
};
vendorHash = "sha256-pY5m5ODRgqghyELRwwxOr+xlW41gtJWLXaW53GlLaFw=";
# Tests require git worktree operations that fail in Nix sandbox
doCheck = false;
})

View File

@@ -1,18 +0,0 @@
{prev}:
prev.n8n.overrideAttrs (oldAttrs: rec {
version = "2.4.1";
src = prev.fetchFromGitHub {
owner = "n8n-io";
repo = "n8n";
rev = "n8n@${version}";
hash = "sha256-EQP9ZI8kt30SUYE1+/UUpxQXpavzKqDu8qE24zsNifg=";
};
pnpmDeps = prev.pnpm_10.fetchDeps {
pname = oldAttrs.pname;
inherit version src;
fetcherVersion = 1;
hash = "sha256-Q30IuFEQD3896Hg0HCLd38YE2i8fJn74JY0o95LKJis=";
};
})