devenv
Initialize and manage Nix flake development environments with auto-detection and security tooling
From devenvnpx claudepluginhub jskswamy/claude-plugins --plugin devenvThis skill is limited to using the following tools:
Devenv Command
Initialize and manage Nix flake development environments. Provides a devbox-like experience with auto-detection of project dependencies and security tooling using native Nix solutions.
Execution Flow
Step 1: Check Current State
First, determine if this is a new setup or managing an existing environment:
- Check if
flake.nixexists in the current directory - If flake.nix exists, check if
git-hooksinput is present (indicates pre-commit is configured) - Read
.claude/devenv.local.mdif it exists for:- channel preference (default:
nixos-unstable) - shell welcome style (default:
box) - custom welcome text (if
shell_welcome: custom)
- channel preference (default:
Step 2: Branch Based on State
If flake.nix does NOT exist (Initialize Flow)
2a. Auto-detect project stack:
Scan for language indicator files and suggest packages:
| File | Stack | Suggested Packages |
|---|---|---|
package.json | Node.js | nodejs, check lockfile for pnpm/yarn/npm |
pnpm-lock.yaml | Node.js + pnpm | nodejs, pnpm |
yarn.lock | Node.js + yarn | nodejs, yarn |
package-lock.json | Node.js + npm | nodejs |
Cargo.toml | Rust | rustc, cargo, rust-analyzer |
go.mod | Go | go, gopls |
pyproject.toml | Python | python3, uv |
requirements.txt | Python | python3, pip |
Gemfile | Ruby | ruby, bundler |
pom.xml | Java | jdk, maven |
build.gradle | Java/Kotlin | jdk, gradle |
*.csproj | .NET | dotnet-sdk |
Makefile | C/C++ | gnumake, gcc |
CMakeLists.txt | C/C++ | cmake, gcc |
Dockerfile | Docker | docker |
Also read CLAUDE.md if it exists to understand the project context and tech stack.
2b. Present detected packages:
Use AskUserQuestion with multiSelect to show detected packages:
Based on your repository, I detected these packages:
[x] nodejs_22
[x] pnpm
[x] typescript
Let user confirm, deselect, or add more via "Other" option.
2c. Ask for additional packages:
Ask: "Any additional packages you need? (e.g., redis, postgresql, jq)"
For each package mentioned:
- Check
.claude/devenv.local.mdforuse_mcp_searchsetting (default:true) - If MCP search is enabled, try MCP tools in priority order:
a. 1mcp (preferred — already running, no extra process):
b. Global/project level MCP (if user has mcp-nixos configured):mcp__1mcp__nixos_1mcp_nixos_search( query="<package>", search_type="packages", channel="unstable", limit=10 )
c. Plugin's bundled MCP:mcp__nixos__nix( action="search", query="<package>", source="nixos", type="packages", channel="unstable", limit=10 )mcp__plugin_devenv_nixos__nix( action="search", query="<package>", source="nixos", type="packages", channel="unstable", limit=10 ) - Fallback: If no MCP tool available or
use_mcp_search: false, use bash:nix-shell -p nix --run "nix search nixpkgs <package> --json" - If version specified (e.g.,
nodejs@20), handle version resolution: a. Trymcp__1mcp__nixos_1mcp_nixhub_find_version(package_name="nodejs", version="20")b. If 1mcp unavailable, trymcp__plugin_devenv_nixos__nix_versions(package="nodejs", version="20")c. Also search for nixpkgs variants likenodejs_20d. Prefer the nixpkgs variant if it exists; offer pinned commit hash as alternative - Present search results and confirm selection
2d. Offer security tools:
Use AskUserQuestion with multiSelect:
Security tools (recommended):
[ ] pre-commit - Git hooks with SAST tools for your stack (using git-hooks.nix)
If pre-commit selected, determine which git-hooks.nix hooks to enable based on detected stack:
| Stack | Hooks to Enable |
|---|---|
| JavaScript/TypeScript | eslint.enable = true; prettier.enable = true; |
| Python | ruff.enable = true; ruff-format.enable = true; |
| Go | golangci-lint.enable = true; |
| Rust | clippy.enable = true; rustfmt.enable = true; |
| Nix (always) | nixfmt-rfc-style.enable = true; statix.enable = true; deadnix.enable = true; |
| Shell scripts | shellcheck.enable = true; |
| Docker | hadolint.enable = true; |
| General (always) | check-yaml.enable = true; trim-trailing-whitespace.enable = true; |
Then ask about secret scanning:
Use AskUserQuestion:
Secret scanner preference:
○ trufflehog (Recommended) - Built-in hook, works out of the box
○ gitleaks - Popular choice, requires custom hook configuration
○ None - Skip secret scanning
If trufflehog selected:
- Add
trufflehog.enable = true;to the hooks configuration (built-in, no extra config needed)
If gitleaks selected:
- Add gitleaks as a custom hook (it's NOT built-in to git-hooks.nix):
gitleaks = {
enable = true;
name = "gitleaks";
entry = "${pkgs.gitleaks}/bin/gitleaks git --pre-commit --redact --staged --verbose";
language = "system";
pass_filenames = false;
};
- Also add
gitleaksto the packages list - If pre-commit not selected, still add
gitleaksto packages for manual use
2e. Choose direnv mode:
Use AskUserQuestion:
direnv mode:
○ direnv-instant (Recommended) - Async loading, instant shell prompt
○ Standard direnv - Traditional sync direnv (shell waits for Nix)
○ None - Use nix develop manually
Based on selection:
- direnv-instant: Add
direnv-instantas a flake input (it's NOT a nixpkgs package):
Then reference the package in devShell:inputs.direnv-instant.url = "github:Mic92/direnv-instant";direnv-instant.packages.${system}.default - Standard direnv: Add
direnvto packages list in flake.nix (from nixpkgs) - None: Don't add direnv package, skip .envrc handling
Save the selection to .claude/devenv.local.md as direnv_mode: instant|standard|none
2f. Choose shell welcome style:
Use AskUserQuestion to let the user choose their shell welcome message style:
Shell welcome message style:
○ Box style (Default) - Unicode box with devenv branding
○ Minimal - Single line "▸ devenv ready"
○ Project name - Shows current project directory name
○ Tech style - Terminal-inspired "[devenv] :: environment initialized"
○ Custom - Enter your own message
○ None - No welcome message
If "Custom" is selected:
- Use AskUserQuestion to ask: "Enter your custom welcome message (use \n for multiple lines):"
- Store the custom text for shellHook generation
Save the selection to .claude/devenv.local.md as:
shell_welcome: box|minimal|project|tech|custom|noneshell_welcome_custom: "user's custom message"(only if custom selected)
2g. Generate files:
- Generate
flake.nixusing the appropriate template:
Basic Template (without pre-commit):
{
description = "Development environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
# DIRENV_INSTANT_INPUT_HERE (if selected)
};
outputs = {
self,
nixpkgs,
flake-utils,
# DIRENV_INSTANT_PARAM_HERE (if selected)
}:
flake-utils.lib.eachDefaultSystem (
system: let
pkgs = nixpkgs.legacyPackages.${system};
in {
devShells.default = pkgs.mkShell {
packages = with pkgs; [
# PACKAGES_HERE
]
# DIRENV_INSTANT_PACKAGE_HERE (if selected)
;
shellHook = ''
# SHELL_WELCOME_HERE
'';
};
}
);
}
To add direnv-instant to the template:
- Replace
# DIRENV_INSTANT_INPUT_HEREwith:direnv-instant.url = "github:Mic92/direnv-instant"; - Replace
# DIRENV_INSTANT_PARAM_HEREwith:direnv-instant, - Replace
# DIRENV_INSTANT_PACKAGE_HEREwith:++ [ direnv-instant.packages.${system}.default ]
To add standard direnv: Simply add direnv to the packages list (it's a nixpkgs package).
Template with git-hooks.nix (pre-commit enabled):
{
description = "Development environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
git-hooks.url = "github:cachix/git-hooks.nix";
# DIRENV_INSTANT_INPUT_HERE (if selected)
};
outputs = {
self,
nixpkgs,
flake-utils,
git-hooks,
# DIRENV_INSTANT_PARAM_HERE (if selected)
}:
flake-utils.lib.eachDefaultSystem (
system: let
pkgs = nixpkgs.legacyPackages.${system};
# Pre-commit hooks configuration
pre-commit-check = git-hooks.lib.${system}.run {
src = ./.;
hooks = {
# Nix
nixfmt-rfc-style.enable = true;
statix.enable = true;
deadnix = {
enable = true;
settings.edit = true;
settings.noLambdaPatternNames = true; # Preserve 'self' in flake outputs
};
# General
check-yaml.enable = true;
trim-trailing-whitespace.enable = true;
# LANGUAGE_HOOKS_HERE
# SECRET_SCANNER_HERE
# For trufflehog (built-in): trufflehog.enable = true;
# For gitleaks (custom hook):
# gitleaks = {
# enable = true;
# name = "gitleaks";
# entry = "${pkgs.gitleaks}/bin/gitleaks git --pre-commit --redact --staged --verbose";
# language = "system";
# pass_filenames = false;
# };
};
};
in {
# Run hooks in CI with: nix flake check
checks.pre-commit-check = pre-commit-check;
devShells.default = pkgs.mkShell {
packages = with pkgs; [
# PACKAGES_HERE
]
++ pre-commit-check.enabledPackages
# DIRENV_INSTANT_PACKAGE_HERE (if selected)
;
shellHook = ''
${pre-commit-check.shellHook}
# SHELL_WELCOME_HERE
'';
};
}
);
}
direnv-instant integration: Same as basic template - add input, parameter, and package reference.
Shell welcome replacement: Replace # SHELL_WELCOME_HERE based on user's shell_welcome preference:
| Style | Replacement Content |
|---|---|
box | echo "╔══════════════════════════════════════╗"<br>echo "║ 🔧 devenv ║"<br>echo "║ Development environment loaded ║"<br>echo "╚══════════════════════════════════════╝" |
minimal | echo ""<br>echo " ▸ devenv ready"<br>echo "" |
project | echo ""<br>echo " ╭───────────────────────────────────╮"<br>echo " │ 📁 $(basename $PWD)"<br>echo " │ Development environment loaded"<br>echo " ╰───────────────────────────────────╯"<br>echo "" |
tech | echo ""<br>echo " [devenv] :: environment initialized"<br>echo " └── packages loaded, hooks active"<br>echo "" |
custom | Parse shell_welcome_custom from settings, split by \n, wrap each line in echo "..." |
none | # No welcome message (empty comment, no output) |
For custom messages:
-
Read
shell_welcome_customfrom.claude/devenv.local.md -
Split by
\nfor multi-line support -
Wrap each line in
echo "..."statements -
Escape special characters:
$becomes\$,"becomes\",\becomes\\ -
If empty, fall back to
boxstyle -
Handle
.envrc(if direnv mode is not "none"):IMPORTANT: Never read
.envrcfiles - they may contain secrets.- Check if
.envrcexists:test -f .envrc - If
.envrcdoes NOT exist:- Create it with content:
use flake
- Create it with content:
- If
.envrcEXISTS:- Do NOT read the file
- Tell the user: "
.envrcalready exists. Ensure it containsuse flakefor direnv integration."
- Check if
-
Update
.gitignoreto exclude generated/sensitive files:- Check if
.gitignoreexists - Append entries that are not already present:
- Check if
# Nix/direnv
.direnv/
.pre-commit-config.yaml
# Environment files (sensitive)
.env
.env.*
.envrc
# Claude local settings (user preferences)
.claude/*.local.md
- Run
nix flake lockto generateflake.lock
2h. Validate the flake:
After generating files, ALWAYS validate the flake works correctly:
nix flake check --no-build 2>&1
If validation fails:
- Read the error message carefully
- Common issues to check:
- Missing
selfparameter in outputs (required by flake system even if unused) - Syntax errors in Nix expressions
- Invalid package names
- Missing inputs referenced in outputs
- Missing
- Fix the issue in
flake.nix - Re-run validation until it passes
- Only proceed to output instructions after validation succeeds
Important: Never finish the task if validation fails. The user should have a working environment.
2i. Output instructions:
If direnv-instant was selected:
Development environment created!
To enter the environment:
nix develop
Or with direnv-instant (async, instant prompt):
direnv allow
direnv-instant shell setup (one-time):
# bash (~/.bashrc) or zsh (~/.zshrc):
eval "$(direnv-instant hook bash)" # or zsh
# fish (~/.config/fish/config.fish):
direnv-instant hook fish | source
Note: Remove any existing `eval "$(direnv hook ...)"` first.
Your environment includes:
- [list packages]
If standard direnv was selected:
Development environment created!
To enter the environment:
nix develop
Or with direnv:
direnv allow
Your environment includes:
- [list packages]
If direnv mode is "none":
Development environment created!
To enter the environment:
nix develop
Your environment includes:
- [list packages]
[If pre-commit was selected, append:]
Pre-commit hooks are configured via git-hooks.nix.
They will auto-install when you run `nix develop`.
Available hooks:
- [list enabled hooks]
To run hooks manually: pre-commit run --all-files
To validate in CI: nix flake check
If flake.nix EXISTS (Manage Flow)
2a. Parse current state:
- Read
flake.nixand extract current packages frompackages = with pkgs; [ ... ] - Check if
git-hooksinput exists in flake.nix (indicates pre-commit is configured)
2b. Ask what to do:
Use AskUserQuestion:
What would you like to do?
○ Add packages - Add new packages to your environment
○ Upgrade packages - Update flake.lock to get latest versions
○ Remove packages - Remove packages from your environment
○ Setup security tools - Add pre-commit and gitleaks (only if not configured)
○ Switch direnv mode - Change between direnv-instant, standard direnv, or none
○ Change welcome style - Customize shell welcome message
Only show "Setup security tools" option if git-hooks input is NOT present in flake.nix.
Show "Switch direnv mode" and "Change welcome style" options always (user may want to change their preference).
2c. Handle selected action:
Add packages:
- Ask: "What packages would you like to add?"
- Search nixpkgs for each package:
- Check
.claude/devenv.local.mdforuse_mcp_searchsetting (default:true) - If MCP search is enabled, try MCP tools in priority order:
a. 1mcp (preferred):
mcp__1mcp__nixos_1mcp_nixos_search(query="<pkg>", search_type="packages", channel="unstable", limit=10)b. Global/project MCP:mcp__nixos__nix(action="search", query="<pkg>", ...)c. Plugin's bundled MCP:mcp__plugin_devenv_nixos__nix(action="search", query="<pkg>", ...) - Fallback: If no MCP tool available or
use_mcp_search: false, use bash:nix-shell -p nix --run "nix search nixpkgs <package> --json" - For versioned packages (e.g.,
nodejs@20): a. Trymcp__1mcp__nixos_1mcp_nixhub_find_version(package_name="nodejs", version="20")b. If 1mcp unavailable, trymcp__plugin_devenv_nixos__nix_versions(package="nodejs", version="20")c. Also search for nixpkgs variants likenodejs_20
- Check
- Confirm package names
- Edit
flake.nixto add packages to the list - Run
nix flake lock --update-input nixpkgsto update lock
Upgrade packages:
- Run
nix flake updateto update all inputs - Show what was updated from the output
Remove packages:
- Show current packages list with checkboxes (multiSelect)
- Let user select which to remove
- Edit
flake.nixto remove selected packages
Setup security tools:
- Follow the same flow as step 2d from Initialize Flow
- Add
git-hooksinput to flake.nix - Add the
pre-commit-checkconfiguration - Update the devShell to include
pre-commit-check.enabledPackagesand shellHook - Add the
checks.pre-commit-checkoutput
Switch direnv mode:
- Check current direnv configuration in flake.nix:
- Look for
direnv-instantin inputs (it's a flake input, NOT a nixpkgs package) - Look for
direnvin packages list (this IS a nixpkgs package)
- Look for
- Use AskUserQuestion:
direnv mode: ○ direnv-instant (Recommended) - Async loading, instant shell prompt ○ Standard direnv - Traditional sync direnv (shell waits for Nix) ○ None - Use nix develop manually - Based on selection:
- direnv-instant:
- Add
direnv-instant.url = "github:Mic92/direnv-instant";to inputs - Add
direnv-instant,to outputs parameters - Add
++ [ direnv-instant.packages.${system}.default ]to packages - Remove
direnvfrom packages if present
- Add
- Standard direnv:
- Remove
direnv-instantinput, parameter, and package reference if present - Add
direnvto packages list (from nixpkgs)
- Remove
- None:
- Remove
direnv-instantinput, parameter, and package reference if present - Remove
direnvfrom packages if present
- Remove
- direnv-instant:
- Update
.claude/devenv.local.mdwith newdirenv_modesetting - Show appropriate shell hook instructions:
- For direnv-instant: Show the
eval "$(direnv-instant hook ...)"instructions - For standard direnv: Remind to use
eval "$(direnv hook ...)" - For none: Note that user should use
nix developmanually
- For direnv-instant: Show the
Change welcome style:
- Read current
shell_welcomesetting from.claude/devenv.local.md(default:box) - Use AskUserQuestion:
Shell welcome message style: ○ Box style (Default) - Unicode box with devenv branding ○ Minimal - Single line "▸ devenv ready" ○ Project name - Shows current project directory name ○ Tech style - Terminal-inspired "[devenv] :: environment initialized" ○ Custom - Enter your own message ○ None - No welcome message - If "Custom" selected, ask for custom message text
- Update
.claude/devenv.local.mdwith newshell_welcome(andshell_welcome_customif applicable) - Update the
shellHooksection inflake.nix:- Read the current flake.nix
- Locate the shellHook block in devShells.default
- Replace the welcome message echo statements using the replacement table from Step 2g
- Preserve any
${pre-commit-check.shellHook}if present at the start of shellHook - Keep any other shellHook content intact
- Show the user a preview of their new welcome message
After any modification, validate the flake:
nix flake check --no-build 2>&1
If validation fails, fix the issue before completing the task.
Important Notes
- Always use
nix-shell -p <tool> --run "<command>"for nix tools - do NOT assume they are installed system-wide - The PostToolUse hooks will automatically format and lint
flake.nixafter any Write/Edit operations - Use the nixpkgs channel from
.claude/devenv.local.mdif it exists, otherwise default tonixos-unstable - When searching for packages, use 1mcp NixOS tools first, then mcp-nixos MCP, with bash fallback
git-hooks.nixauto-generates.pre-commit-config.yamlvia shellHook - no manual YAML needed!- All hook tools are provided by Nix, ensuring reproducibility
- Critical: The
selfparameter in flake outputs is REQUIRED by the Nix flake system, even if not explicitly used in the function body. The plugin's deadnix hook is configured with--no-lambda-pattern-namesto preserve it - Always validate with
nix flake check --no-buildbefore completing any flake creation/modification task - direnv-instant is a flake input, NOT a nixpkgs package: It must be added via
inputs.direnv-instant.url = "github:Mic92/direnv-instant"and referenced asdirenv-instant.packages.${system}.default. StandarddirenvIS in nixpkgs and can be added directly to the packages list.
MCP Tools: NixOS Package Search
This plugin uses NixOS MCP tools for package search, providing access to 130K+ NixOS packages with accurate, up-to-date information.
Tool Priority Order
The command tries MCP tools in this order:
- 1mcp (preferred):
mcp__1mcp__nixos_1mcp_nixos_search— already running via 1mcp proxy, no extra process - Global/Project level:
mcp__nixos__nix— if user has mcp-nixos configured globally or in project - Plugin's bundled:
mcp__plugin_devenv_nixos__nix— plugin's own MCP server (fallback for non-1mcp environments) - Bash fallback:
nix searchcommand
This ensures the fastest available tool is used first, with graceful degradation.
Package Search
1mcp (separate tools per action):
mcp__1mcp__nixos_1mcp_nixos_search(
query="<search_term>",
search_type="packages",
channel="unstable",
limit=10
)
Global/Plugin MCP (combined action parameter):
mcp__nixos__nix( # or mcp__plugin_devenv_nixos__nix
action="search",
query="<search_term>",
source="nixos",
type="packages",
channel="unstable",
limit=10
)
Version Search
For versioned package requests (e.g., nodejs@20):
1mcp — find specific version:
mcp__1mcp__nixos_1mcp_nixhub_find_version(
package_name="nodejs",
version="20"
)
1mcp — list all available versions:
mcp__1mcp__nixos_1mcp_nixhub_package_versions(
package_name="nodejs",
limit=20
)
Plugin bundled MCP — version lookup:
mcp__plugin_devenv_nixos__nix_versions(
package="nodejs",
version="20"
)
Version resolution workflow:
- Try
nixhub_find_version(ornix_versions) to find the exact version - Also search nixpkgs for variants like
nodejs_20 - Prefer the nixpkgs variant if it exists (simpler, no pinning needed)
- Offer pinned commit hash from nixhub as alternative if no nixpkgs variant found
Parameter Reference
| Tool | Parameter | Value | Description |
|---|---|---|---|
1mcp_nixos_search | query | string | Package name or search term |
1mcp_nixos_search | search_type | "packages" | Search packages (not options) |
1mcp_nixos_search | channel | "unstable" | Nixpkgs channel |
1mcp_nixos_search | limit | number | Max results to return |
1mcp_nixhub_find_version | package_name | string | Package to find version for |
1mcp_nixhub_find_version | version | string | Desired version (e.g., "20") |
1mcp_nixhub_package_versions | package_name | string | Package to list versions for |
1mcp_nixhub_package_versions | limit | number | Max versions to return |
nixos__nix / plugin_devenv_nixos__nix | action | "search" | Search for packages |
nixos__nix / plugin_devenv_nixos__nix | query | string | Package name or search term |
nixos__nix / plugin_devenv_nixos__nix | source | "nixos" | Use NixOS packages source |
nixos__nix / plugin_devenv_nixos__nix | type | "packages" | Search packages (not options) |
nixos__nix / plugin_devenv_nixos__nix | channel | "unstable" | Use nixos-unstable channel |
nixos__nix / plugin_devenv_nixos__nix | limit | number | Max results to return |
plugin_devenv_nixos__nix_versions | package | string | Package to find version for |
plugin_devenv_nixos__nix_versions | version | string | Desired version (optional) |
plugin_devenv_nixos__nix_versions | limit | number | Max versions to return (optional) |
Fallback Behavior
If no MCP tool is available, the command falls back to:
nix-shell -p nix --run "nix search nixpkgs <package> --json"
Settings File
Users can customize behavior via .claude/devenv.local.md:
---
nixpkgs_channel: nixos-unstable
use_mcp_search: true
direnv_mode: instant
shell_welcome: box
shell_welcome_custom: ""
---
Available Settings
| Setting | Default | Description |
|---|---|---|
nixpkgs_channel | nixos-unstable | Nixpkgs channel to use |
use_mcp_search | true | Use mcp-nixos for package search. Set to false to always use bash fallback |
direnv_mode | instant | direnv integration: instant (async, recommended), standard (sync), or none |
shell_welcome | box | Welcome message style: box, minimal, project, tech, custom, or none |
shell_welcome_custom | "" | Custom welcome message text (only used when shell_welcome: custom). Use \n for multi-line |
git-hooks.nix Hook Reference
Available Hooks by Language
| Language | Hook Names |
|---|---|
| JavaScript/TypeScript | eslint, prettier |
| Python | ruff, ruff-format, black, mypy |
| Go | golangci-lint, gofmt |
| Rust | clippy, rustfmt, cargo-check |
| Nix | nixfmt-rfc-style, statix, deadnix |
| Shell | shellcheck, shfmt |
| Docker | hadolint |
| YAML | check-yaml, yamllint |
| Secrets | trufflehog, ripsecrets (built-in); gitleaks requires custom config |
| General | trim-trailing-whitespace, check-added-large-files, check-merge-conflicts |
Example Hook Configuration
hooks = {
# Enable a built-in hook
eslint.enable = true;
# Built-in secret scanner
trufflehog.enable = true;
# Enable with custom settings
ruff = {
enable = true;
settings.args = ["--fix"];
};
# Custom hook example: gitleaks (not built-in)
gitleaks = {
enable = true;
name = "gitleaks";
entry = "${pkgs.gitleaks}/bin/gitleaks git --pre-commit --redact --staged --verbose";
language = "system";
pass_filenames = false;
};
};
For full list of available hooks, see: https://github.com/cachix/git-hooks.nix