github-copilot-cli: add module

This commit is contained in:
Josef Hofer
2026-04-17 15:24:31 +02:00
committed by Austin Horstman
parent f92e976f40
commit 503480e513
11 changed files with 392 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
_: {
time = "2026-04-17T15:06:14+00:00";
condition = true;
message = ''
A new module is available: 'programs.github-copilot-cli'.
GitHub Copilot CLI brings the agentic Copilot coding experience to the
terminal. The module manages the `~/.copilot/config.json` settings file
(model, theme, trusted folders, hooks, feature flags, etc.) and the
`~/.copilot/mcp-config.json` MCP server registry. Setting
`enableMcpIntegration = true` reuses servers defined under
`programs.mcp.servers`, with `programs.github-copilot-cli.mcpServers`
taking precedence.
'';
}

View File

@@ -0,0 +1,200 @@
{
lib,
pkgs,
config,
...
}:
let
inherit (lib)
literalExpression
mkEnableOption
mkIf
mkOption
mkPackageOption
;
cfg = config.programs.github-copilot-cli;
jsonFormat = pkgs.formats.json { };
upstreamConfigDir = "${config.home.homeDirectory}/.copilot";
transformSingleServer =
_name: server:
let
base = removeAttrs server [ "disabled" ];
withType =
if base ? type then
base
else if base ? url then
base // { type = "http"; }
else
base // { type = "local"; };
withTools = if withType ? tools then withType else withType // { tools = [ "*" ]; };
in
withTools;
transformedMcpServers =
if cfg.enableMcpIntegration && config.programs.mcp.enable && config.programs.mcp.servers != { } then
lib.mapAttrs transformSingleServer (
lib.filterAttrs (
_: server: !(server.disabled or false) && (server ? url || server ? command)
) config.programs.mcp.servers
)
else
{ };
mergedMcpServers = transformedMcpServers // cfg.mcpServers;
in
{
meta.maintainers = [ lib.maintainers.ojsef39 ];
options.programs.github-copilot-cli = {
enable = mkEnableOption "GitHub Copilot CLI";
package = mkPackageOption pkgs "github-copilot-cli" { nullable = true; };
configDir = mkOption {
type = lib.types.str;
default =
if config.home.preferXdgDirectories then "${config.xdg.configHome}/copilot" else upstreamConfigDir;
defaultText = literalExpression ''
if config.home.preferXdgDirectories then
"''${config.xdg.configHome}/copilot"
else
"''${config.home.homeDirectory}/.copilot"
'';
example = literalExpression ''"''${config.xdg.configHome}/copilot"'';
description = ''
Directory holding Copilot CLI configuration files such as
{file}`config.json` and {file}`mcp-config.json`.
Defaults to `''${config.xdg.configHome}/copilot` when
{option}`home.preferXdgDirectories` is enabled and to `~/.copilot`
otherwise. The {env}`COPILOT_HOME` environment variable is exported
automatically whenever the directory differs from the upstream
default of `~/.copilot`.
See <https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-config-dir-reference#changing-the-location-of-the-configuration-directory>.
'';
};
enableMcpIntegration = mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to integrate the MCP servers config from
{option}`programs.mcp.servers` into
{option}`programs.github-copilot-cli.mcpServers`.
Servers defined in {option}`programs.mcp.servers` are merged with
{option}`programs.github-copilot-cli.mcpServers`, with the latter
taking precedence. Disabled servers (where `disabled = true`) are
excluded from the generated configuration.
'';
};
settings = mkOption {
type = lib.types.attrsOf jsonFormat.type;
default = { };
example = literalExpression ''
{
model = "claude-sonnet-4-5";
theme = "default";
trusted_folders = [ "/home/user/projects" ];
renderMarkdown = true;
autoUpdate = false;
}
'';
description = ''
Configuration written to {file}`config.json` inside
{option}`programs.github-copilot-cli.configDir`.
Known configuration keys include:
- `model` AI model selection
- `effortLevel` reasoning effort for capable models
- `theme` `"default"`, `"dim"`, `"high-contrast"`, or `"colorblind"`
- `mouse` enable mouse support (default: `true`)
- `banner` frequency of animated banner display
- `renderMarkdown` markdown rendering toggle (default: `true`)
- `screenReader` accessibility optimizations (default: `false`)
- `autoUpdate` automatic CLI updates (default: `true`)
- `stream` token-by-token response streaming (default: `true`)
- `includeCoAuthoredBy` agent commit attribution (default: `true`)
- `respectGitignore` exclude gitignored files from file picker
- `trusted_folders` list of pre-approved directory paths
- `allowed_urls`, `denied_urls` URL allowlists/blocklists
- `logLevel` log verbosity
- `disableAllHooks` global hook disable toggle
- `hooks` inline hook definitions
- `enabledFeatureFlags` enable or disable specific feature flags
See <https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-config-dir-reference>
for the documentation.
'';
};
mcpServers = mkOption {
type = lib.types.attrsOf jsonFormat.type;
default = { };
example = literalExpression ''
{
playwright = {
type = "local";
command = "npx";
args = [ "@playwright/mcp@latest" ];
tools = [ "*" ];
};
context7 = {
type = "http";
url = "https://mcp.context7.com/mcp";
headers = { CONTEXT7_API_KEY = "YOUR-API-KEY"; };
tools = [ "*" ];
};
}
'';
description = ''
MCP server configurations written to {file}`mcp-config.json` inside
{option}`programs.github-copilot-cli.configDir`.
Each attribute defines a server entry under `mcpServers` in the config
file. Supported server types:
- `local` starts a local process via stdio (`command`, optional `args`, `env`)
- `http` connects to a remote HTTP server (`url`, optional `headers`)
- `sse` legacy HTTP with Server-Sent Events (same structure as `http`)
The `tools` field accepts `["*"]` to enable all tools or a list of
specific tool names.
See <https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers>
for the documentation.
'';
};
};
config = mkIf cfg.enable {
home.packages = mkIf (cfg.package != null) [ cfg.package ];
home.file = {
# NOTE: Copilot will try to add a firstLaunchAt date and crash if the
# file exists but does not have this key set. Only generate the file when
# the user has explicitly configured settings, and always inject the
# default so the managed file stays valid.
"${cfg.configDir}/config.json" = mkIf (cfg.settings != { }) {
source = jsonFormat.generate "github-copilot-cli-config.json" (
{ firstLaunchAt = "1970-01-01T00:00:00.000Z"; } // cfg.settings
);
};
"${cfg.configDir}/mcp-config.json" = mkIf (mergedMcpServers != { }) {
source = jsonFormat.generate "github-copilot-cli-mcp-config.json" {
mcpServers = mergedMcpServers;
};
};
};
home.sessionVariables = mkIf (cfg.configDir != upstreamConfigDir) {
COPILOT_HOME = cfg.configDir;
};
};
}

View File

@@ -68,6 +68,7 @@ let
"git-credential-oauth"
"git-lfs"
"git-worktree-switcher"
"github-copilot-cli"
"gitMinimal"
"gnupg"
"go"

View File

@@ -0,0 +1,17 @@
{
programs.github-copilot-cli = {
enable = true;
settings = {
model = "claude-sonnet-4-5";
theme = "dark";
trusted_folders = [ "/home/user/projects" ];
};
};
nmt.script = ''
assertFileExists home-files/.copilot/config.json
assertFileContent home-files/.copilot/config.json ${./expected-config.json}
assertPathNotExists home-files/.copilot/mcp-config.json
assertFileNotRegex home-path/etc/profile.d/hm-session-vars.sh 'COPILOT_HOME'
'';
}

View File

@@ -0,0 +1,6 @@
{
github-copilot-cli-config = ./config.nix;
github-copilot-cli-mcp = ./mcp.nix;
github-copilot-cli-mcp-integration = ./mcp-integration.nix;
github-copilot-cli-xdg-config-dir = ./xdg-config-dir.nix;
}

View File

@@ -0,0 +1,8 @@
{
"firstLaunchAt": "1970-01-01T00:00:00.000Z",
"model": "claude-sonnet-4-5",
"theme": "dark",
"trusted_folders": [
"/home/user/projects"
]
}

View File

@@ -0,0 +1,21 @@
{
"mcpServers": {
"context7": {
"tools": [
"*"
],
"type": "http",
"url": "https://mcp.context7.com/mcp"
},
"playwright": {
"args": [
"@playwright/mcp@latest"
],
"command": "npx",
"tools": [
"*"
],
"type": "local"
}
}
}

View File

@@ -0,0 +1,30 @@
{
"mcpServers": {
"database": {
"args": [
"-y",
"@bytebase/dbhub"
],
"command": "npx",
"env": {
"DATABASE_URL": "postgresql://user:pass@localhost:5432/db"
},
"tools": [
"*"
],
"type": "local"
},
"filesystem": {
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/tmp"
],
"command": "npx",
"tools": [
"*"
],
"type": "local"
}
}
}

View File

@@ -0,0 +1,51 @@
{
programs = {
github-copilot-cli = {
enable = true;
enableMcpIntegration = true;
# user-defined server takes precedence over the integrated one
mcpServers.filesystem = {
type = "local";
command = "npx";
args = [
"-y"
"@modelcontextprotocol/server-filesystem"
"/tmp"
];
tools = [ "*" ];
};
};
mcp = {
enable = true;
servers = {
filesystem = {
command = "npx";
args = [
"-y"
"@modelcontextprotocol/server-filesystem"
"/other-tmp"
];
};
database = {
command = "npx";
args = [
"-y"
"@bytebase/dbhub"
];
env = {
DATABASE_URL = "postgresql://user:pass@localhost:5432/db";
};
};
disabled-server = {
command = "disabled-cmd";
disabled = true;
};
};
};
};
nmt.script = ''
assertFileExists home-files/.copilot/mcp-config.json
assertFileContent home-files/.copilot/mcp-config.json ${./expected-mcp-integration-config.json}
'';
}

View File

@@ -0,0 +1,24 @@
{
programs.github-copilot-cli = {
enable = true;
mcpServers = {
playwright = {
type = "local";
command = "npx";
args = [ "@playwright/mcp@latest" ];
tools = [ "*" ];
};
context7 = {
type = "http";
url = "https://mcp.context7.com/mcp";
tools = [ "*" ];
};
};
};
nmt.script = ''
assertFileExists home-files/.copilot/mcp-config.json
assertFileContent home-files/.copilot/mcp-config.json ${./expected-mcp-config.json}
assertPathNotExists home-files/.copilot/config.json
'';
}

View File

@@ -0,0 +1,19 @@
{
home.preferXdgDirectories = true;
programs.github-copilot-cli = {
enable = true;
settings = {
model = "claude-sonnet-4-5";
theme = "dark";
trusted_folders = [ "/home/user/projects" ];
};
};
nmt.script = ''
assertFileExists home-files/.config/copilot/config.json
assertFileContent home-files/.config/copilot/config.json ${./expected-config.json}
assertPathNotExists home-files/.copilot/config.json
assertFileContains home-path/etc/profile.d/hm-session-vars.sh \
'export COPILOT_HOME="/home/hm-user/.config/copilot"'
'';
}