1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-11-08 11:36:05 +01:00

Compare commits

..

9 commits

Author SHA1 Message Date
Austin Horstman
aa6936bb63 tests/yazi: fix bash/zsh integration tests
Didn't fail even with incorrect assertion. Multi line string for
assertFileContains didn't properly work. Don't want to manage an entire
zsh config file in assertFileContent so just multi step asserting the
generated file.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-11-03 21:54:12 -06:00
meck
65bf99c579 yazi: update wrappers not to use cat in subshell
If cat is aliased to bat the non piping operation
in the script might include extra text, use builtins
instead
2025-11-03 21:54:12 -06:00
Austin Horstman
6feb368511 news: add vicinae entry
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-11-03 21:18:48 -06:00
leiserfg
5cdf9ef995 vicinae service: init
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2025-11-03 21:18:48 -06:00
Thierry Delafontaine
1342b821db news: add entry for mcp module and integrations
Add news entry documenting the new `programs.mcp` module and MCP
integration support in OpenCode and VSCode modules.
2025-11-03 20:25:16 -06:00
Thierry Delafontaine
9ff9a94fd4 vscode: add mcp module integration 2025-11-03 20:25:16 -06:00
Thierry Delafontaine
c740351870 opencode: add mcp module integration 2025-11-03 20:25:16 -06:00
Thierry Delafontaine
083b20c1a0 mcp: init module 2025-11-03 20:25:16 -06:00
Pierre-Yves Landuré
1f34c2c855 zed-editor: options to generate debug.json
Add `mutableUserDebug` and `userDebug` options to generate `debug.json` file.
The options are heavily inspired by `mutableUserTasks` and `userTasks` options implementation.

Closes #8091
2025-11-03 20:24:19 -06:00
32 changed files with 1272 additions and 42 deletions

View file

@ -0,0 +1,25 @@
{
time = "2025-11-03T10:18:15+00:00";
condition = true;
message = ''
A new module 'programs.mcp' is now available for managing Model
Context Protocol (MCP) server configurations.
The 'programs.mcp.servers' option allows you to define MCP servers
in a central location. These configurations can be automatically
integrated into applications that support MCP.
Two modules now support MCP integration:
- 'programs.opencode.enableMcpIntegration': Integrates MCP servers
into OpenCode's configuration.
- 'programs.vscode.profiles.<name>.enableMcpIntegration': Integrates
MCP servers into VSCode profiles.
When integration is enabled, servers from 'programs.mcp.servers' are
merged with application-specific MCP settings, with the latter taking
precedence. This allows you to define MCP servers once and reuse them
across multiple applications.
'';
}

View file

@ -0,0 +1,20 @@
{ pkgs, ... }:
{
time = "2025-11-04T02:33:15+00:00";
condition = pkgs.stdenv.hostPlatform.isLinux;
message = ''
A new program is available: 'programs.vicinae'.
Vicinae is a modern application launcher daemon for Linux with support for
extensions, custom themes, and layer shell integration.
The module provides:
- Systemd service integration with automatic start support
- Extension management with helpers for Vicinae and Raycast extensions
- Theme configuration support
- Declarative settings via 'programs.vicinae.settings'
- Layer shell integration for Wayland compositors
See the module options for more details on configuration.
'';
}

64
modules/programs/mcp.nix Normal file
View file

@ -0,0 +1,64 @@
{
config,
lib,
pkgs,
...
}:
let
inherit (lib)
literalExpression
mkEnableOption
mkIf
mkOption
;
cfg = config.programs.mcp;
jsonFormat = pkgs.formats.json { };
in
{
meta.maintainers = with lib.maintainers; [ delafthi ];
options.programs.mcp = {
enable = mkEnableOption "mcp";
servers = mkOption {
inherit (jsonFormat) type;
default = { };
example = literalExpression ''
{
everything = {
command = "npx";
args = [
"-y"
"@modelcontextprotocol/server-everything"
];
};
context7 = {
url = "https://mcp.context7.com/mcp";
headers = {
CONTEXT7_API_KEY = "{env:CONTEXT7_API_KEY}";
};
};
}
'';
description = ''
MCP server configurations written to
{file}`XDG_CONFIG_HOME/.config/mcp/mcp.json`
'';
};
};
config = mkIf cfg.enable {
xdg.configFile = mkIf (cfg.servers != { }) (
let
mcp-config = {
mcpServers = cfg.servers;
};
in
{
"mcp/mcp.json".source = jsonFormat.generate "mcp.json" mcp-config;
}
);
};
}

View file

@ -16,6 +16,35 @@ let
cfg = config.programs.opencode;
jsonFormat = pkgs.formats.json { };
transformMcpServer = name: server: {
name = name;
value = {
enabled = !(server.disabled or false);
}
// (
if server ? url then
{
type = "remote";
url = server.url;
}
// (lib.optionalAttrs (server ? headers) { headers = server.headers; })
else if server ? command then
{
type = "local";
command = [ server.command ] ++ (server.args or [ ]);
}
// (lib.optionalAttrs (server ? env) { environment = server.env; })
else
{ }
);
};
transformedMcpServers =
if cfg.enableMcpIntegration && config.programs.mcp.enable && config.programs.mcp.servers != { } then
lib.listToAttrs (lib.mapAttrsToList transformMcpServer config.programs.mcp.servers)
else
{ };
in
{
meta.maintainers = with lib.maintainers; [ delafthi ];
@ -25,6 +54,20 @@ in
package = mkPackageOption pkgs "opencode" { nullable = true; };
enableMcpIntegration = mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to integrate the MCP servers config from
{option}`programs.mcp.servers` into
{option}`programs.opencode.settings.mcp`.
Note: Settings defined in {option}`programs.mcp.servers` are merged
with {option}`programs.opencode.settings.mcp`, with OpenCode settings
taking precedence.
'';
};
settings = mkOption {
inherit (jsonFormat) type;
default = { };
@ -147,7 +190,7 @@ in
Custom themes for opencode. The attribute name becomes the theme
filename, and the value is either:
- An attribute set, that is converted to a json
- A path to a file conaining the content
- A path to a file containing the content
Themes are stored in {file}`$XDG_CONFIG_HOME/opencode/themes/` directory.
Set `programs.opencode.settings.theme` to enable the custom theme.
See <https://opencode.ai/docs/themes/> for the documentation.
@ -159,13 +202,21 @@ in
home.packages = mkIf (cfg.package != null) [ cfg.package ];
xdg.configFile = {
"opencode/config.json" = mkIf (cfg.settings != { }) {
source = jsonFormat.generate "config.json" (
{
"$schema" = "https://opencode.ai/config.json";
}
// cfg.settings
);
"opencode/config.json" = mkIf (cfg.settings != { } || transformedMcpServers != { }) {
source =
let
# Merge MCP servers: transformed servers + user settings, with user settings taking precedence
mergedMcpServers = transformedMcpServers // (cfg.settings.mcp or { });
# Merge all settings
mergedSettings =
cfg.settings // (lib.optionalAttrs (mergedMcpServers != { }) { mcp = mergedMcpServers; });
in
jsonFormat.generate "config.json" (
{
"$schema" = "https://opencode.ai/config.json";
}
// mergedSettings
);
};
"opencode/AGENTS.md" = (

View file

@ -0,0 +1,244 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.programs.vicinae;
jsonFormat = pkgs.formats.json { };
in
{
meta.maintainers = [ lib.maintainers.leiserfg ];
options.programs.vicinae = {
enable = lib.mkEnableOption "vicinae launcher daemon";
package = lib.mkPackageOption pkgs "vicinae" { nullable = true; };
systemd = {
enable = lib.mkEnableOption "vicinae systemd integration";
autoStart = lib.mkOption {
type = lib.types.bool;
default = true;
description = "If the vicinae daemon should be started automatically";
};
target = lib.mkOption {
type = lib.types.str;
default = "graphical-session.target";
example = "sway-session.target";
description = ''
The systemd target that will automatically start the vicinae service.
'';
};
};
useLayerShell = lib.mkOption {
type = lib.types.bool;
default = true;
description = "If vicinae should use the layer shell";
};
extensions = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ ];
description = ''
List of Vicinae extensions to install.
You can use the `config.lib.vicinae.mkExtension` and `config.lib.vicinae.mkRayCastExtension` functions to create them, like:
```nix
[
(config.lib.vicinae.mkExtension {
name = "test-extension";
src =
pkgs.fetchFromGitHub {
owner = "schromp";
repo = "vicinae-extensions";
rev = "f8be5c89393a336f773d679d22faf82d59631991";
sha256 = "sha256-zk7WIJ19ITzRFnqGSMtX35SgPGq0Z+M+f7hJRbyQugw=";
}
+ "/test-extension";
})
(config.lib.vicinae.mkRayCastExtension {
name = "gif-search";
sha256 = "sha256-G7il8T1L+P/2mXWJsb68n4BCbVKcrrtK8GnBNxzt73Q=";
rev = "4d417c2dfd86a5b2bea202d4a7b48d8eb3dbaeb1";
})
],
```
'';
};
themes = lib.mkOption {
inherit (jsonFormat) type;
default = { };
description = ''
Theme settings to add to the themes folder in `~/.config/vicinae/themes`.
The attribute name of the theme will be the name of theme json file,
e.g. `base16-default-dark` will be `base16-default-dark.json`.
'';
example =
lib.literalExpression # nix
''
{
base16-default-dark = {
version = "1.0.0";
appearance = "dark";
icon = /path/to/icon.png;
name = "base16 default dark";
description = "base16 default dark by Chris Kempson";
palette = {
background = "#181818";
foreground = "#d8d8d8";
blue = "#7cafc2";
green = "#a3be8c";
magenta = "#ba8baf";
orange = "#dc9656";
purple = "#a16946";
red = "#ab4642";
yellow = "#f7ca88";
cyan = "#86c1b9";
};
};
}
'';
};
settings = lib.mkOption {
inherit (jsonFormat) type;
default = { };
description = "Settings written as JSON to `~/.config/vicinae/vicinae.json.";
example = lib.literalExpression ''
{
faviconService = "twenty";
font = {
size = 10;
};
popToRootOnClose = false;
rootSearch = {
searchFiles = false;
};
theme = {
name = "vicinae-dark";
};
window = {
csd = true;
opacity = 0.95;
rounding = 10;
};
}
'';
};
};
config = lib.mkIf cfg.enable {
assertions = [
(lib.hm.assertions.assertPlatform "programs.vicinae" pkgs lib.platforms.linux)
{
assertion = cfg.systemd.enable -> cfg.package != null;
message = "{option}programs.vicinae.systemd.enable requires non null {option}programs.vicinae.package";
}
];
lib.vicinae.mkExtension = (
{
name,
src,
}:
(pkgs.buildNpmPackage {
inherit name src;
installPhase = ''
runHook preInstall
mkdir -p $out
cp -r /build/.local/share/vicinae/extensions/${name}/* $out/
runHook postInstall
'';
npmDeps = pkgs.importNpmLock { npmRoot = src; };
npmConfigHook = pkgs.importNpmLock.npmConfigHook;
})
);
lib.vicinae.mkRayCastExtension = (
{
name,
sha256,
rev,
}:
let
src =
pkgs.fetchgit {
inherit rev sha256;
url = "https://github.com/raycast/extensions";
sparseCheckout = [
"/extensions/${name}"
];
}
+ "/extensions/${name}";
in
(pkgs.buildNpmPackage {
inherit name src;
installPhase = ''
runHook preInstall
mkdir -p $out
cp -r /build/.config/raycast/extensions/${name}/* $out/
runHook postInstall
'';
npmDeps = pkgs.importNpmLock { npmRoot = src; };
npmConfigHook = pkgs.importNpmLock.npmConfigHook;
})
);
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];
xdg = {
configFile = {
"vicinae/vicinae.json" = lib.mkIf (cfg.settings != { }) {
source = jsonFormat.generate "vicinae-settings" cfg.settings;
};
}
// lib.mapAttrs' (
name: theme:
lib.nameValuePair "vicinae/themes/${name}.json" {
source = jsonFormat.generate "vicinae-${name}-theme" theme;
}
) cfg.themes;
dataFile = builtins.listToAttrs (
builtins.map (item: {
name = "vicinae/extensions/${item.name}";
value.source = item;
}) cfg.extensions
);
};
systemd.user.services.vicinae = lib.mkIf (cfg.systemd.enable && cfg.package != null) {
Unit = {
Description = "Vicinae server daemon";
Documentation = [ "https://docs.vicinae.com" ];
After = [ cfg.systemd.target ];
PartOf = [ cfg.systemd.target ];
BindsTo = [ cfg.systemd.target ];
};
Service = {
EnvironmentFile = pkgs.writeText "vicinae-env" ''
USE_LAYER_SHELL=${if cfg.useLayerShell then builtins.toString 1 else builtins.toString 0}
'';
Type = "simple";
ExecStart = "${lib.getExe' cfg.package "vicinae"} server";
Restart = "always";
RestartSec = 5;
KillMode = "process";
};
Install = lib.mkIf cfg.systemd.autoStart {
WantedBy = [ cfg.systemd.target ];
};
};
};
}

View file

@ -114,6 +114,33 @@ let
isPath = p: builtins.isPath p || lib.isStorePath p;
transformMcpServerForVscode =
name: server:
let
# Remove the disabled field from the server config
cleanServer = lib.filterAttrs (n: v: n != "disabled") server;
in
{
name = name;
value = {
enabled = !(server.disabled or false);
}
// (
if server ? url then
{
type = "http";
}
// cleanServer
else if server ? command then
{
type = "stdio";
}
// cleanServer
else
{ }
);
};
profileType = types.submodule {
options = {
userSettings = mkOption {
@ -154,6 +181,20 @@ let
'';
};
enableMcpIntegration = mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to integrate the MCP servers config from
{option}`programs.mcp.servers` into
{option}`programs.vscode.profiles.<name>.userMcp`.
Note: Settings defined in {option}`programs.mcp.servers` are merged
with {option}`programs.vscode.profiles.<name>.userMcp`, with VSCode
settings taking precedence.
'';
};
userMcp = mkOption {
type = types.either types.path jsonFormat.type;
default = { };
@ -459,10 +500,31 @@ in
if isPath v.userTasks then v.userTasks else jsonFormat.generate "vscode-user-tasks" v.userTasks;
})
(mkIf (v.userMcp != { }) {
"${mcpFilePath n}".source =
if isPath v.userMcp then v.userMcp else jsonFormat.generate "vscode-user-mcp" v.userMcp;
})
(mkIf
(
v.userMcp != { }
|| (v.enableMcpIntegration && config.programs.mcp.enable && config.programs.mcp.servers != { })
)
{
"${mcpFilePath n}".source =
if isPath v.userMcp then
v.userMcp
else
let
transformedMcpServers =
if v.enableMcpIntegration && config.programs.mcp.enable && config.programs.mcp.servers != { } then
lib.listToAttrs (lib.mapAttrsToList transformMcpServerForVscode config.programs.mcp.servers)
else
{ };
# Merge MCP servers: transformed servers + user servers, with user servers taking precedence
mergedServers = transformedMcpServers // ((v.userMcp.servers or { }));
# Merge all MCP config
mergedMcpConfig =
v.userMcp // (lib.optionalAttrs (mergedServers != { }) { servers = mergedServers; });
in
jsonFormat.generate "vscode-user-mcp" mergedMcpConfig;
}
)
(mkIf (v.keybindings != [ ]) {
"${keybindingsFilePath n}".source =

View file

@ -221,7 +221,7 @@ in
function ${cfg.shellWrapperName}() {
local tmp="$(mktemp -t "yazi-cwd.XXXXX")"
yazi "$@" --cwd-file="$tmp"
if cwd="$(cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then
if cwd="$(<"$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then
builtin cd -- "$cwd"
fi
rm -f -- "$tmp"
@ -231,7 +231,7 @@ in
fishIntegration = ''
set -l tmp (mktemp -t "yazi-cwd.XXXXX")
command yazi $argv --cwd-file="$tmp"
if set cwd (cat -- "$tmp"); and [ -n "$cwd" ]; and [ "$cwd" != "$PWD" ]
if read cwd < "$tmp"; and [ -n "$cwd" ]; and [ "$cwd" != "$PWD" ]
builtin cd -- "$cwd"
end
rm -f -- "$tmp"

View file

@ -81,6 +81,15 @@ in
'';
};
mutableUserDebug = mkOption {
type = types.bool;
default = true;
example = false;
description = ''
Whether user debug configurations (debug.json) can be updated by zed.
'';
};
userSettings = mkOption {
type = jsonFormat.type;
default = { };
@ -140,6 +149,27 @@ in
'';
};
userDebug = mkOption {
type = jsonFormat.type;
default = [ ];
example = literalExpression ''
[
{
label = "Go (Delve)";
adapter = "Delve";
program = "$ZED_FILE";
request = "launch";
mode = "debug";
}
]
'';
description = ''
Configuration written to Zed's {file}`debug.json`.
Global debug configurations for Zed's [Debugger](https://zed.dev/docs/debugger).
'';
};
extensions = mkOption {
type = types.listOf types.str;
default = [ ];
@ -241,6 +271,14 @@ in
(jsonFormat.generate "zed-user-tasks" cfg.userTasks)
);
})
(mkIf (cfg.mutableUserDebug && cfg.userDebug != [ ]) {
zedDebugActivation = lib.hm.dag.entryAfter [ "linkGeneration" ] (
impureConfigMerger "[]"
"$dynamic + $static | group_by(.label) | map(reduce .[] as $item ({}; . * $item))"
"${config.xdg.configHome}/zed/debug.json"
(jsonFormat.generate "zed-user-debug" cfg.userDebug)
);
})
];
xdg.configFile = mkMerge [
@ -265,6 +303,9 @@ in
(mkIf (!cfg.mutableUserTasks && cfg.userTasks != [ ]) {
"zed/tasks.json".source = jsonFormat.generate "zed-user-tasks" cfg.userTasks;
})
(mkIf (!cfg.mutableUserDebug && cfg.userDebug != [ ]) {
"zed/debug.json".source = jsonFormat.generate "zed-user-debug" cfg.userDebug;
})
];
assertions = [

View file

@ -0,0 +1,4 @@
{
mcp-servers = ./servers.nix;
mcp-empty-servers = ./empty-servers.nix;
}

View file

@ -0,0 +1,9 @@
{
programs.mcp = {
enable = true;
servers = { };
};
nmt.script = ''
assertPathNotExists home-files/.config/mcp/mcp.json
'';
}

View file

@ -0,0 +1,17 @@
{
"mcpServers": {
"context7": {
"headers": {
"CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}"
},
"serverUrl": "https://mcp.context7.com/mcp"
},
"everything": {
"args": [
"-y",
"@modelcontextprotocol/server-everything"
],
"command": "npx"
}
}
}

View file

@ -0,0 +1,25 @@
{
programs.mcp = {
enable = true;
servers = {
everything = {
command = "npx";
args = [
"-y"
"@modelcontextprotocol/server-everything"
];
};
context7 = {
serverUrl = "https://mcp.context7.com/mcp";
headers = {
CONTEXT7_API_KEY = "{env:CONTEXT7_API_KEY}";
};
};
};
};
nmt.script = ''
assertFileExists home-files/.config/mcp/mcp.json
assertFileContent home-files/.config/mcp/mcp.json \
${./mcp.json}
'';
}

View file

@ -11,4 +11,6 @@
opencode-mixed-content = ./mixed-content.nix;
opencode-themes-inline = ./themes-inline.nix;
opencode-themes-path = ./themes-path.nix;
opencode-mcp-integration = ./mcp-integration.nix;
opencode-mcp-integration-with-override = ./mcp-integration-with-override.nix;
}

View file

@ -0,0 +1,27 @@
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"context7": {
"enabled": true,
"headers": {
"CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}"
},
"type": "remote",
"url": "https://mcp.context7.com/mcp"
},
"custom-server": {
"enabled": true,
"type": "remote",
"url": "https://example.com"
},
"everything": {
"command": [
"custom-command"
],
"enabled": false,
"type": "local"
}
},
"model": "anthropic/claude-sonnet-4-20250514",
"theme": "opencode"
}

View file

@ -0,0 +1,48 @@
{
programs.mcp = {
enable = true;
servers = {
everything = {
command = "npx";
args = [
"-y"
"@modelcontextprotocol/server-everything"
];
};
context7 = {
url = "https://mcp.context7.com/mcp";
headers = {
CONTEXT7_API_KEY = "{env:CONTEXT7_API_KEY}";
};
};
};
};
programs.opencode = {
enable = true;
enableMcpIntegration = true;
settings = {
theme = "opencode";
model = "anthropic/claude-sonnet-4-20250514";
# User's custom MCP settings should override generated ones
mcp = {
everything = {
enabled = false; # Override to disable
command = [ "custom-command" ];
type = "local";
};
custom-server = {
enabled = true;
type = "remote";
url = "https://example.com";
};
};
};
};
nmt.script = ''
assertFileExists home-files/.config/opencode/config.json
assertFileContent home-files/.config/opencode/config.json \
${./mcp-integration-with-override.json}
'';
}

View file

@ -0,0 +1,30 @@
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"context7": {
"enabled": true,
"headers": {
"CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}"
},
"type": "remote",
"url": "https://mcp.context7.com/mcp"
},
"disabled-server": {
"command": [
"echo",
"test"
],
"enabled": false,
"type": "local"
},
"everything": {
"command": [
"npx",
"-y",
"@modelcontextprotocol/server-everything"
],
"enabled": true,
"type": "local"
}
}
}

View file

@ -0,0 +1,36 @@
{
programs.mcp = {
enable = true;
servers = {
everything = {
command = "npx";
args = [
"-y"
"@modelcontextprotocol/server-everything"
];
};
context7 = {
url = "https://mcp.context7.com/mcp";
headers = {
CONTEXT7_API_KEY = "{env:CONTEXT7_API_KEY}";
};
};
disabled-server = {
command = "echo";
args = [ "test" ];
disabled = true;
};
};
};
programs.opencode = {
enable = true;
enableMcpIntegration = true;
};
nmt.script = ''
assertFileExists home-files/.config/opencode/config.json
assertFileContent home-files/.config/opencode/config.json \
${./mcp-integration.json}
'';
}

View file

@ -0,0 +1,4 @@
{ lib, pkgs, ... }:
lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux) {
vicinae-example-settings = ./example-settings.nix;
}

View file

@ -0,0 +1,77 @@
{
pkgs,
config,
...
}:
{
programs.vicinae = {
enable = true;
systemd.enable = true;
settings = {
faviconService = "twenty";
font = {
size = 10;
};
popToRootOnClose = false;
rootSearch = {
searchFiles = false;
};
theme = {
name = "vicinae-dark";
};
window = {
csd = true;
opacity = 0.95;
rounding = 10;
};
};
themes = {
base16-default-dark = {
version = "1.0.0";
appearance = "dark";
name = "base16 default dark";
description = "base16 default dark by Chris Kempson";
palette = {
background = "#181818";
foreground = "#d8d8d8";
blue = "#7cafc2";
green = "#a3be8c";
magenta = "#ba8baf";
orange = "#dc9656";
purple = "#a16946";
red = "#ab4642";
yellow = "#f7ca88";
cyan = "#86c1b9";
};
};
};
extensions = [
(config.lib.vicinae.mkRayCastExtension {
name = "gif-search";
sha256 = "sha256-G7il8T1L+P/2mXWJsb68n4BCbVKcrrtK8GnBNxzt73Q=";
rev = "4d417c2dfd86a5b2bea202d4a7b48d8eb3dbaeb1";
})
(config.lib.vicinae.mkExtension {
name = "test-extension";
src =
pkgs.fetchFromGitHub {
owner = "schromp";
repo = "vicinae-extensions";
rev = "f8be5c89393a336f773d679d22faf82d59631991";
sha256 = "sha256-zk7WIJ19ITzRFnqGSMtX35SgPGq0Z+M+f7hJRbyQugw=";
}
+ "/test-extension";
})
];
};
nmt.script = ''
assertFileExists "home-files/.config/vicinae/vicinae.json"
assertFileExists "home-files/.config/systemd/user/vicinae.service"
assertFileExists "home-files/.local/share/vicinae/extensions/gif-search/package.json"
assertFileExists "home-files/.local/share/vicinae/extensions/test-extension/package.json"
'';
}

View file

@ -24,6 +24,8 @@ let
keybindings = import ./keybindings.nix;
tasks = import ./tasks.nix;
mcp = import ./mcp.nix;
mcp-integration = import ./mcp-integration.nix;
mcp-integration-with-override = import ./mcp-integration-with-override.nix;
update-checks = import ./update-checks.nix;
snippets = import ./snippets.nix;
};

View file

@ -0,0 +1,26 @@
{
"servers": {
"context7": {
"enabled": true,
"headers": {
"CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}"
},
"type": "http",
"url": "https://mcp.context7.com/mcp"
},
"disabled-server": {
"command": "echo",
"enabled": false,
"type": "stdio"
},
"everything": {
"args": [
"-y",
"@modelcontextprotocol/server-everything"
],
"command": "npx",
"enabled": true,
"type": "stdio"
}
}
}

View file

@ -0,0 +1,7 @@
{
"servers": {
"Github": {
"url": "https://api.githubcopilot.com/mcp/"
}
}
}

View file

@ -0,0 +1,25 @@
{
"servers": {
"CustomServer": {
"type": "http",
"url": "https://example.com/mcp"
},
"context7": {
"enabled": true,
"headers": {
"CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}"
},
"type": "http",
"url": "https://mcp.context7.com/mcp"
},
"everything": {
"args": [
"-y",
"@modelcontextprotocol/server-everything"
],
"command": "custom-npx",
"enabled": false,
"type": "stdio"
}
}
}

View file

@ -0,0 +1,79 @@
package:
{
config,
pkgs,
lib,
...
}:
let
cfg = config.programs.vscode;
willUseIfd = package.pname != "vscode";
mcpFilePath =
name:
if pkgs.stdenv.hostPlatform.isDarwin then
"Library/Application Support/${cfg.nameShort}/User/${
lib.optionalString (name != "default") "profiles/${name}/"
}mcp.json"
else
".config/${cfg.nameShort}/User/${
lib.optionalString (name != "default") "profiles/${name}/"
}mcp.json";
in
lib.mkIf (willUseIfd -> config.test.enableLegacyIfd) {
programs.mcp = {
enable = true;
servers = {
everything = {
command = "npx";
args = [
"-y"
"@modelcontextprotocol/server-everything"
];
};
context7 = {
url = "https://mcp.context7.com/mcp";
headers = {
CONTEXT7_API_KEY = "{env:CONTEXT7_API_KEY}";
};
};
};
};
programs.vscode = {
enable = true;
inherit package;
profiles = {
default = {
enableMcpIntegration = true;
# User MCP settings should override generated ones
userMcp = {
servers = {
everything = {
command = "custom-npx";
args = [
"-y"
"@modelcontextprotocol/server-everything"
];
enabled = false;
type = "stdio";
};
CustomServer = {
type = "http";
url = "https://example.com/mcp";
};
};
};
};
};
};
nmt.script = ''
assertFileExists "home-files/${mcpFilePath "default"}"
assertFileContent "home-files/${mcpFilePath "default"}" ${./mcp-integration-with-override.json}
'';
}

View file

@ -0,0 +1,73 @@
package:
{
config,
pkgs,
lib,
...
}:
let
cfg = config.programs.vscode;
willUseIfd = package.pname != "vscode";
mcpFilePath =
name:
if pkgs.stdenv.hostPlatform.isDarwin then
"Library/Application Support/${cfg.nameShort}/User/${
lib.optionalString (name != "default") "profiles/${name}/"
}mcp.json"
else
".config/${cfg.nameShort}/User/${
lib.optionalString (name != "default") "profiles/${name}/"
}mcp.json";
in
lib.mkIf (willUseIfd -> config.test.enableLegacyIfd) {
programs.mcp = {
enable = true;
servers = {
everything = {
command = "npx";
args = [
"-y"
"@modelcontextprotocol/server-everything"
];
};
context7 = {
url = "https://mcp.context7.com/mcp";
headers = {
CONTEXT7_API_KEY = "{env:CONTEXT7_API_KEY}";
};
};
disabled-server = {
command = "echo";
disabled = true;
};
};
};
programs.vscode = {
enable = true;
inherit package;
profiles = {
default.enableMcpIntegration = true;
test.userMcp = {
servers = {
Github = {
url = "https://api.githubcopilot.com/mcp/";
};
};
};
};
};
nmt.script = ''
assertFileExists "home-files/${mcpFilePath "default"}"
assertFileContent "home-files/${mcpFilePath "default"}" ${./mcp-integration-default.json}
assertFileExists "home-files/${mcpFilePath "test"}"
assertFileContent "home-files/${mcpFilePath "test"}" ${./mcp-integration-test.json}
'';
}

View file

@ -1,15 +1,3 @@
let
shellIntegration = ''
function yy() {
local tmp="$(mktemp -t "yazi-cwd.XXXXX")"
yazi "$@" --cwd-file="$tmp"
if cwd="$(cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then
builtin cd -- "$cwd"
fi
rm -f -- "$tmp"
}
'';
in
{
programs.bash.enable = true;
@ -19,6 +7,12 @@ in
};
nmt.script = ''
assertFileContains home-files/.bashrc '${shellIntegration}'
assertFileExists home-files/.bashrc
assertFileContains home-files/.bashrc 'function yy() {'
assertFileContains home-files/.bashrc 'local tmp="$(mktemp -t "yazi-cwd.XXXXX")"'
assertFileContains home-files/.bashrc 'yazi "$@" --cwd-file="$tmp"'
assertFileContains home-files/.bashrc 'if cwd="$(<"$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then'
assertFileContains home-files/.bashrc 'builtin cd -- "$cwd"'
assertFileContains home-files/.bashrc 'rm -f -- "$tmp"'
'';
}

View file

@ -1,7 +1,7 @@
function yy
set -l tmp (mktemp -t "yazi-cwd.XXXXX")
command yazi $argv --cwd-file="$tmp"
if set cwd (cat -- "$tmp"); and [ -n "$cwd" ]; and [ "$cwd" != "$PWD" ]
if read cwd <"$tmp"; and [ -n "$cwd" ]; and [ "$cwd" != "$PWD" ]
builtin cd -- "$cwd"
end
rm -f -- "$tmp"

View file

@ -1,24 +1,18 @@
let
shellIntegration = ''
function yy() {
local tmp="$(mktemp -t "yazi-cwd.XXXXX")"
yazi "$@" --cwd-file="$tmp"
if cwd="$(cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then
builtin cd -- "$cwd"
fi
rm -f -- "$tmp"
}
'';
in
{
programs.zsh.enable = true;
programs.yazi = {
enable = true;
enableBashIntegration = true;
enableZshIntegration = true;
};
nmt.script = ''
assertFileContains home-files/.zshrc '${shellIntegration}'
assertFileExists home-files/.zshrc
assertFileContains home-files/.zshrc 'function yy() {'
assertFileContains home-files/.zshrc 'local tmp="$(mktemp -t "yazi-cwd.XXXXX")"'
assertFileContains home-files/.zshrc 'yazi "$@" --cwd-file="$tmp"'
assertFileContains home-files/.zshrc 'if cwd="$(<"$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then'
assertFileContains home-files/.zshrc 'builtin cd -- "$cwd"'
assertFileContains home-files/.zshrc 'rm -f -- "$tmp"'
'';
}

View file

@ -0,0 +1,83 @@
{
config,
lib,
pkgs,
...
}:
{
programs.zed-editor = {
enable = true;
package = config.lib.test.mkStubPackage { };
userDebug = [
{
label = "PHP: Listen to Xdebug";
adapter = "Xdebug";
request = "launch";
port = 9003;
}
{
label = "PHP: Debug this test";
adapter = "Xdebug";
request = "launch";
program = "vendor/bin/phpunit";
args = [
"--filter"
"$ZED_SYMBOL"
];
}
];
};
home.homeDirectory = lib.mkForce "/@TMPDIR@/hm-user";
nmt.script =
let
preexistingDebug = builtins.toFile "preexisting.json" "";
expectedContent = builtins.toFile "expected.json" ''
[
{
"adapter": "Xdebug",
"args": [
"--filter",
"$ZED_SYMBOL"
],
"label": "PHP: Debug this test",
"program": "vendor/bin/phpunit",
"request": "launch"
},
{
"adapter": "Xdebug",
"label": "PHP: Listen to Xdebug",
"port": 9003,
"request": "launch"
}
]
'';
debugPath = ".config/zed/debug.json";
activationScript = pkgs.writeScript "activation" config.home.activation.zedDebugActivation.data;
in
''
export HOME=$TMPDIR/hm-user
# Simulate preexisting debug
mkdir -p $HOME/.config/zed
cat ${preexistingDebug} > $HOME/${debugPath}
# Run the activation script
substitute ${activationScript} $TMPDIR/activate --subst-var TMPDIR
chmod +x $TMPDIR/activate
$TMPDIR/activate
# Validate the merged debug
assertFileExists "$HOME/${debugPath}"
assertFileContent "$HOME/${debugPath}" "${expectedContent}"
# Test idempotency
$TMPDIR/activate
assertFileExists "$HOME/${debugPath}"
assertFileContent "$HOME/${debugPath}" "${expectedContent}"
'';
}

View file

@ -0,0 +1,58 @@
# Test custom keymap functionality
{ config, ... }:
{
programs.zed-editor = {
enable = true;
package = config.lib.test.mkStubPackage { };
mutableUserDebug = false;
userDebug = [
{
label = "PHP: Listen to Xdebug";
adapter = "Xdebug";
request = "launch";
port = 9003;
}
{
label = "PHP: Debug this test";
adapter = "Xdebug";
request = "launch";
program = "vendor/bin/phpunit";
args = [
"--filter"
"$ZED_SYMBOL"
];
}
];
};
nmt.script =
let
expectedContent = builtins.toFile "expected.json" ''
[
{
"adapter": "Xdebug",
"label": "PHP: Listen to Xdebug",
"port": 9003,
"request": "launch"
},
{
"adapter": "Xdebug",
"args": [
"--filter",
"$ZED_SYMBOL"
],
"label": "PHP: Debug this test",
"program": "vendor/bin/phpunit",
"request": "launch"
}
]
'';
settingsPath = ".config/zed/debug.json";
in
''
assertFileExists "home-files/${settingsPath}"
assertFileContent "home-files/${settingsPath}" "${expectedContent}"
'';
}

View file

@ -0,0 +1,100 @@
{
config,
lib,
pkgs,
...
}:
{
programs.zed-editor = {
enable = true;
package = config.lib.test.mkStubPackage { };
userDebug = [
{
label = "PHP: Listen to Xdebug";
adapter = "Xdebug";
request = "launch";
port = 9003;
}
{
label = "PHP: Debug this test";
adapter = "Xdebug";
request = "launch";
program = "vendor/bin/phpunit";
args = [
"--filter"
"$ZED_SYMBOL"
];
}
];
};
home.homeDirectory = lib.mkForce "/@TMPDIR@/hm-user";
nmt.script =
let
preexistingDebug = builtins.toFile "preexisting.json" ''
[
{
"label": "Debug active Python file",
"adapter": "Debugpy",
"program": "$ZED_FILE",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT"
}
]
'';
expectedContent = builtins.toFile "expected.json" ''
[
{
"label": "Debug active Python file",
"adapter": "Debugpy",
"program": "$ZED_FILE",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT"
},
{
"adapter": "Xdebug",
"args": [
"--filter",
"$ZED_SYMBOL"
],
"label": "PHP: Debug this test",
"program": "vendor/bin/phpunit",
"request": "launch"
},
{
"adapter": "Xdebug",
"label": "PHP: Listen to Xdebug",
"port": 9003,
"request": "launch"
}
]
'';
debugPath = ".config/zed/debug.json";
activationScript = pkgs.writeScript "activation" config.home.activation.zedDebugActivation.data;
in
''
export HOME=$TMPDIR/hm-user
# Simulate preexisting debug
mkdir -p $HOME/.config/zed
cat ${preexistingDebug} > $HOME/${debugPath}
# Run the activation script
substitute ${activationScript} $TMPDIR/activate --subst-var TMPDIR
chmod +x $TMPDIR/activate
$TMPDIR/activate
# Validate the merged debug
assertFileExists "$HOME/${debugPath}"
assertFileContent "$HOME/${debugPath}" "${expectedContent}"
# Test idempotency
$TMPDIR/activate
assertFileExists "$HOME/${debugPath}"
assertFileContent "$HOME/${debugPath}" "${expectedContent}"
'';
}

View file

@ -11,5 +11,8 @@
zed-tasks = ./tasks.nix;
zed-tasks-immutable = ./tasks-immutable.nix;
zed-tasks-empty = ./tasks-empty.nix;
zed-debug = ./debug.nix;
zed-debug-immutable = ./debug-immutable.nix;
zed-debug-empty = ./debug-empty.nix;
zed-themes = ./themes;
}