mirror of
https://github.com/nix-community/home-manager.git
synced 2026-11-07 14:59:33 +00:00
Keep native settings and MCP configuration writable while applying declared values. Treat unspecified permission lists as absent in mutable native mode so activation does not reset undeclared application choices. Explicit lists, including empty lists, replace saved lists. Keep immutable and legacy defaults unchanged.
673 lines
22 KiB
Nix
673 lines
22 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
options,
|
|
pkgs,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.programs.antigravity-cli;
|
|
|
|
useGeminiConfig =
|
|
cfg.useLegacyGeminiConfig || (cfg.package != null && lib.getName cfg.package == "gemini-cli");
|
|
|
|
renamedGeminiOptions = [
|
|
"commands"
|
|
"context"
|
|
"defaultModel"
|
|
"enable"
|
|
"enableMcpIntegration"
|
|
"mcpServers"
|
|
"package"
|
|
"permissions"
|
|
"policies"
|
|
"settings"
|
|
"skills"
|
|
"useLegacyGeminiConfig"
|
|
];
|
|
|
|
oldGeminiCliDefined = lib.any (
|
|
name: (lib.getAttrFromPath [ "programs" "gemini-cli" name ] options).isDefined
|
|
) renamedGeminiOptions;
|
|
|
|
jsonFormat = pkgs.formats.json { };
|
|
tomlFormat = pkgs.formats.toml { };
|
|
|
|
transformMcpServer =
|
|
server:
|
|
let
|
|
isRemote =
|
|
(server.httpUrl or null) != null
|
|
|| (server.url or null) != null
|
|
|| (server.serverUrl or null) != null;
|
|
cleaned =
|
|
if isRemote then
|
|
removeAttrs server [
|
|
"command"
|
|
"args"
|
|
"env"
|
|
]
|
|
else
|
|
server;
|
|
isDisabled = lib.hm.mcp.resolveEnabled server == false;
|
|
transformed =
|
|
removeAttrs cleaned [
|
|
"httpUrl"
|
|
"url"
|
|
"enabled"
|
|
]
|
|
// lib.optionalAttrs (server ? httpUrl) {
|
|
serverUrl = server.httpUrl;
|
|
}
|
|
// lib.optionalAttrs (server ? url) {
|
|
serverUrl = server.url;
|
|
}
|
|
//
|
|
lib.optionalAttrs (isDisabled || (cfg.mutableSettings && lib.hm.mcp.resolveEnabled server != null))
|
|
{
|
|
disabled = isDisabled;
|
|
};
|
|
in
|
|
lib.filterAttrs (_: v: v != null && (cfg.mutableSettings || v != [ ]) && v != { }) transformed;
|
|
|
|
transformMcpServers = lib.mapAttrs (_name: transformMcpServer);
|
|
|
|
commandSkillName = lib.replaceStrings [ "/" ] [ ":" ];
|
|
|
|
normalizeSkillsDirectory =
|
|
source:
|
|
if lib.isPath source then
|
|
source
|
|
else
|
|
pkgs.runCommandLocal "antigravity-cli-skills" { } ''
|
|
if [[ ! -d ${lib.escapeShellArg (toString source)} ]]; then
|
|
echo ${lib.escapeShellArg "programs.antigravity-cli.skills must be a directory"} >&2
|
|
exit 1
|
|
fi
|
|
ln -s ${lib.escapeShellArg (toString source)} "$out"
|
|
'';
|
|
|
|
normalizeSkill =
|
|
source:
|
|
pkgs.runCommandLocal "antigravity-cli-skill" { } ''
|
|
source=${lib.escapeShellArg (toString source)}
|
|
if [[ -d "$source" ]]; then
|
|
ln -s "$source" "$out"
|
|
elif [[ -f "$source" ]]; then
|
|
mkdir "$out"
|
|
ln -s "$source" "$out/SKILL.md"
|
|
else
|
|
echo "Antigravity CLI skill source must be a file or directory: $source" >&2
|
|
exit 1
|
|
fi
|
|
'';
|
|
|
|
skillFiles =
|
|
skillsDir:
|
|
if lib.hm.strings.isPathLike cfg.skills then
|
|
{
|
|
"${skillsDir}" = {
|
|
source = normalizeSkillsDirectory cfg.skills;
|
|
recursive = true;
|
|
};
|
|
}
|
|
else
|
|
lib.mapAttrs' (
|
|
name: source:
|
|
if lib.isPath source && lib.pathIsDirectory source then
|
|
lib.nameValuePair "${skillsDir}/${name}" {
|
|
inherit source;
|
|
recursive = true;
|
|
}
|
|
else if lib.hm.strings.isPathLike source && !lib.isPath source then
|
|
lib.nameValuePair "${skillsDir}/${name}" {
|
|
source = normalizeSkill source;
|
|
recursive = true;
|
|
}
|
|
else
|
|
lib.nameValuePair "${skillsDir}/${name}/SKILL.md" (
|
|
if lib.hm.strings.isPathLike source then { inherit source; } else { text = source; }
|
|
)
|
|
) cfg.skills;
|
|
|
|
in
|
|
{
|
|
meta.maintainers = [ lib.maintainers.rrvsh ];
|
|
|
|
imports =
|
|
let
|
|
renamedGeminiOption =
|
|
name:
|
|
lib.mkRenamedOptionModule
|
|
[
|
|
"programs"
|
|
"gemini-cli"
|
|
name
|
|
]
|
|
[
|
|
"programs"
|
|
"antigravity-cli"
|
|
name
|
|
];
|
|
in
|
|
map renamedGeminiOption renamedGeminiOptions;
|
|
|
|
options.programs.antigravity-cli = {
|
|
enable = lib.mkEnableOption "Antigravity CLI";
|
|
|
|
package = lib.mkPackageOption pkgs "antigravity-cli" {
|
|
nullable = true;
|
|
};
|
|
|
|
mutableSettings = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to merge native Antigravity settings and MCP servers into
|
|
writable files during activation. This does not support the legacy
|
|
Gemini CLI configuration or a Gemini CLI package. Gemini CLI supports
|
|
a separate read-only system settings layer via
|
|
{env}`GEMINI_CLI_SYSTEM_SETTINGS_PATH` instead.
|
|
|
|
Declared values, including permission arrays, take precedence over
|
|
existing values. Unset permission lists preserve saved grants; an
|
|
explicit list replaces the saved list, including an empty list.
|
|
MCP files accept comments and trailing commas, but
|
|
are written as JSON without comments or original formatting. Removing
|
|
a declaration does not remove it from the file. Switching back to
|
|
immutable settings only removes byte-identical files; changed files
|
|
require backup or removal.
|
|
'';
|
|
};
|
|
|
|
useLegacyGeminiConfig = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to write configuration to the legacy Gemini CLI locations.
|
|
|
|
This is enabled automatically when
|
|
{option}`programs.antigravity-cli.package` is a `gemini-cli` package.
|
|
'';
|
|
};
|
|
|
|
enableMcpIntegration = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to integrate the MCP servers config from
|
|
{option}`programs.mcp.servers` into
|
|
{option}`programs.antigravity-cli.mcpServers`.
|
|
|
|
Note: Any servers already present in
|
|
{option}`programs.antigravity-cli.mcpServers`
|
|
is not overridden by servers present under the same
|
|
name in {option}`programs.mcp.servers`
|
|
'';
|
|
};
|
|
|
|
settings = lib.mkOption {
|
|
inherit (jsonFormat) type;
|
|
default = { };
|
|
example = {
|
|
colorScheme = "tokyo night";
|
|
altScreenMode = "always";
|
|
toolPermission = "proceed-in-sandbox";
|
|
artifactReviewPolicy = "agent-decides";
|
|
};
|
|
description = ''
|
|
Configuration written to
|
|
{file}`~/.gemini/antigravity-cli/settings.json`.
|
|
'';
|
|
};
|
|
|
|
mcpServers = lib.mkOption {
|
|
inherit (jsonFormat) type;
|
|
default = { };
|
|
example = {
|
|
github = {
|
|
serverUrl = "https://api.githubcopilot.com/mcp/";
|
|
};
|
|
filesystem = {
|
|
command = "npx";
|
|
args = [
|
|
"-y"
|
|
"@modelcontextprotocol/server-filesystem"
|
|
"/tmp"
|
|
];
|
|
};
|
|
};
|
|
description = ''
|
|
MCP server configuration written to
|
|
{file}`~/.gemini/config/mcp_config.json`.
|
|
|
|
Remote servers use `serverUrl`; `url` and `httpUrl` are accepted
|
|
for migration and converted automatically. `enabled` is accepted
|
|
and converted to `disabled`.
|
|
'';
|
|
};
|
|
|
|
commands =
|
|
let
|
|
commandType = lib.types.submodule {
|
|
freeformType = lib.types.str;
|
|
options = {
|
|
prompt = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = ''
|
|
The prompt that will be sent to the Gemini model when the command is executed.
|
|
This can be a single-line or multi-line string.
|
|
The special placeholder {{args}} will be replaced with the text the user typed after the command name.
|
|
'';
|
|
};
|
|
description = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = ''
|
|
A brief, one-line description of what the command does.
|
|
This text will be displayed next to your command in the /help menu.
|
|
If you omit this field, a generic description will be generated from the filename.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
in
|
|
lib.mkOption {
|
|
type = lib.types.attrsOf commandType;
|
|
default = { };
|
|
description = ''
|
|
An attribute set of Gemini CLI custom commands to migrate to
|
|
Antigravity CLI global skills.
|
|
|
|
The name of the attribute set will be the name of each generated
|
|
skill directory.
|
|
'';
|
|
example = lib.literalExpression ''
|
|
changelog = {
|
|
prompt =
|
|
'''
|
|
Your task is to parse the `<version>`, `<change_type>`, and `<message>` from their input and use the `write_file` tool to correctly update the `CHANGELOG.md` file.
|
|
''';
|
|
description = "Adds a new entry to the project's CHANGELOG.md file.";
|
|
};
|
|
"git/fix" = { # Becomes /git:fix
|
|
prompt = "Please analyze the staged git changes and provide a code fix for the issue described here: {{args}}.";
|
|
description = "Generates a fix for a given GitHub issue.";
|
|
};
|
|
'';
|
|
};
|
|
|
|
permissions = lib.mkOption {
|
|
type = lib.types.nullOr (
|
|
lib.types.submodule {
|
|
options = {
|
|
allow = lib.mkOption {
|
|
type = lib.types.nullOr (lib.types.listOf lib.types.str);
|
|
default = if cfg.mutableSettings && !useGeminiConfig then null else [ ];
|
|
defaultText = lib.literalMD "`null` in mutable native mode, otherwise `[ ]`.";
|
|
description = ''
|
|
Permissions to allow without prompting.
|
|
'';
|
|
};
|
|
|
|
deny = lib.mkOption {
|
|
type = lib.types.nullOr (lib.types.listOf lib.types.str);
|
|
default = if cfg.mutableSettings && !useGeminiConfig then null else [ ];
|
|
defaultText = lib.literalMD "`null` in mutable native mode, otherwise `[ ]`.";
|
|
description = ''
|
|
Permissions to deny without prompting.
|
|
'';
|
|
};
|
|
|
|
ask = lib.mkOption {
|
|
type = lib.types.nullOr (lib.types.listOf lib.types.str);
|
|
default = if cfg.mutableSettings && !useGeminiConfig then null else [ ];
|
|
defaultText = lib.literalMD "`null` in mutable native mode, otherwise `[ ]`.";
|
|
description = ''
|
|
Permissions to ask before using.
|
|
'';
|
|
};
|
|
};
|
|
}
|
|
);
|
|
default = null;
|
|
description = ''
|
|
Antigravity CLI fine-grained permissions written to
|
|
{option}`programs.antigravity-cli.settings.permissions`.
|
|
Unspecified lists default to empty lists in immutable and legacy mode.
|
|
In mutable native mode, unspecified lists default to null instead.
|
|
Null lists are omitted from the file, preserving saved values in mutable
|
|
mode; an explicit empty list clears the saved list.
|
|
'';
|
|
example = lib.literalExpression ''
|
|
{
|
|
allow = [ "command(git)" ];
|
|
deny = [ "command(rm -rf)" ];
|
|
ask = [ "command(*)" ];
|
|
}
|
|
'';
|
|
};
|
|
|
|
policies = lib.mkOption {
|
|
type = lib.types.attrsOf (lib.types.either lib.types.path tomlFormat.type);
|
|
default = { };
|
|
description = ''
|
|
An attribute set of Gemini CLI policy definitions to create in
|
|
{file}`~/.gemini/policies/` when
|
|
{option}`programs.antigravity-cli.package` is a `gemini-cli`
|
|
package.
|
|
|
|
Antigravity CLI configures permissions in
|
|
{option}`programs.antigravity-cli.settings.permissions` or
|
|
{option}`programs.antigravity-cli.permissions`.
|
|
'';
|
|
example = lib.literalExpression ''
|
|
{
|
|
"my-rules" = {
|
|
rule = [
|
|
{
|
|
toolName = "run_shell_command";
|
|
commandPrefix = "git ";
|
|
decision = "ask_user";
|
|
priority = 100;
|
|
}
|
|
];
|
|
};
|
|
"other-rules" = ./path/to/rules.toml;
|
|
}
|
|
'';
|
|
};
|
|
|
|
defaultModel = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
example = "gemini-2.5-flash";
|
|
description = ''
|
|
The default model to use for the CLI.
|
|
Will be set as $GEMINI_MODEL when configured.
|
|
'';
|
|
};
|
|
|
|
context = lib.mkOption {
|
|
type = lib.types.attrsOf (lib.types.either lib.types.lines lib.types.path);
|
|
default = { };
|
|
example = lib.literalExpression ''
|
|
{
|
|
GEMINI = '''
|
|
# Global Context
|
|
|
|
You are a helpful AI assistant for software development.
|
|
|
|
## Coding Standards
|
|
|
|
- Follow consistent code style
|
|
- Write clear comments
|
|
- Test your changes
|
|
''';
|
|
|
|
AGENTS = ./path/to/agents.md;
|
|
|
|
CONTEXT = '''
|
|
Additional context instructions here.
|
|
''';
|
|
}
|
|
'';
|
|
description = ''
|
|
Global context(s) for Antigravity CLI.
|
|
|
|
The attribute name becomes the filename, with a {file}`.md`
|
|
extension added automatically. Each value is either:
|
|
- Inline content as a string
|
|
- A path to a file containing the content
|
|
|
|
The configured files are written to {file}`~/.gemini/`.
|
|
|
|
Note: You can customize which context file names Antigravity CLI looks for by setting
|
|
`settings.context.fileName`. For example:
|
|
```nix
|
|
settings = {
|
|
context.fileName = ["AGENTS.md", "CONTEXT.md", "GEMINI.md"];
|
|
};
|
|
```
|
|
'';
|
|
};
|
|
|
|
skills = lib.mkOption {
|
|
type = lib.types.either (lib.types.attrsOf (lib.types.either lib.types.lines lib.types.path)) lib.types.path;
|
|
default = { };
|
|
example = lib.literalExpression ''
|
|
{
|
|
xlsx = ./skills/xlsx/SKILL.md;
|
|
data-analysis = ./skills/data-analysis;
|
|
pdf-processing = '''
|
|
---
|
|
name: pdf-processing
|
|
description: Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction.
|
|
---
|
|
|
|
# PDF Processing
|
|
|
|
## Quick start
|
|
|
|
Use pdfplumber to extract text from PDFs:
|
|
|
|
```python
|
|
import pdfplumber
|
|
|
|
with pdfplumber.open("document.pdf") as pdf:
|
|
text = pdf.pages[0].extract_text()
|
|
```
|
|
''';
|
|
}
|
|
'';
|
|
description = ''
|
|
Custom skills for Antigravity CLI.
|
|
|
|
This option can be either:
|
|
- An attribute set defining skills
|
|
- A path to a directory containing skill folders
|
|
|
|
If an attribute set is used, the attribute name becomes the
|
|
skill directory name, and the value is either:
|
|
- Inline content as a string (creates `~/.gemini/antigravity-cli/skills/<name>/SKILL.md`)
|
|
- A path to a file (creates `~/.gemini/antigravity-cli/skills/<name>/SKILL.md`)
|
|
- A path to a directory (symlinks `~/.gemini/antigravity-cli/skills/<name>/` to that directory)
|
|
|
|
If a path is used, it is expected to contain one folder per
|
|
skill name, each containing a {file}`SKILL.md`. The directory is
|
|
symlinked to {file}`~/.gemini/antigravity-cli/skills/`.
|
|
'';
|
|
};
|
|
};
|
|
|
|
config =
|
|
let
|
|
antigravitySkillsDir = ".gemini/antigravity-cli/skills";
|
|
geminiSkillsDir = ".gemini/skills";
|
|
permissions = lib.optionalAttrs (cfg.permissions != null) (
|
|
lib.filterAttrs (_: value: value != null) cfg.permissions
|
|
);
|
|
antigravitySettings = lib.recursiveUpdate (lib.optionalAttrs (permissions != { }) {
|
|
inherit permissions;
|
|
}) (removeAttrs cfg.settings [ "mcpServers" ]);
|
|
legacyMcpServers = lib.optionalAttrs (cfg.settings ? mcpServers) cfg.settings.mcpServers;
|
|
mcpServers = lib.recursiveUpdate legacyMcpServers cfg.mcpServers;
|
|
json5 = pkgs.python3Packages.toPythonApplication pkgs.python3Packages.json5;
|
|
nativeSettingsFiles =
|
|
lib.optionalAttrs (antigravitySettings != { }) {
|
|
".gemini/antigravity-cli/settings.json" =
|
|
jsonFormat.generate "antigravity-cli-settings.json" antigravitySettings;
|
|
}
|
|
// lib.optionalAttrs (mcpServers != { }) {
|
|
".gemini/config/mcp_config.json" = jsonFormat.generate "antigravity-cli-mcp-config.json" {
|
|
mcpServers = transformMcpServers mcpServers;
|
|
};
|
|
};
|
|
geminiSettings =
|
|
lib.recursiveUpdate
|
|
(lib.optionalAttrs (permissions != { }) {
|
|
inherit permissions;
|
|
})
|
|
(lib.recursiveUpdate cfg.settings (lib.optionalAttrs (mcpServers != { }) { inherit mcpServers; }));
|
|
in
|
|
lib.mkIf cfg.enable (
|
|
lib.mkMerge [
|
|
(lib.mkIf oldGeminiCliDefined {
|
|
programs.antigravity-cli = {
|
|
package = lib.mkDefault pkgs.gemini-cli;
|
|
useLegacyGeminiConfig = lib.mkDefault true;
|
|
};
|
|
})
|
|
{
|
|
home = {
|
|
packages = lib.mkIf (cfg.package != null) [ cfg.package ];
|
|
sessionVariables = lib.mkIf (cfg.defaultModel != null) {
|
|
GEMINI_MODEL = cfg.defaultModel;
|
|
};
|
|
};
|
|
}
|
|
{
|
|
home.file = lib.mapAttrs' (
|
|
n: v: lib.nameValuePair ".gemini/${n}.md" (if lib.isPath v then { source = v; } else { text = v; })
|
|
) cfg.context;
|
|
}
|
|
(lib.mkIf useGeminiConfig {
|
|
assertions = [
|
|
{
|
|
assertion = !cfg.mutableSettings;
|
|
message = "programs.antigravity-cli.mutableSettings only supports native Antigravity CLI configuration, not legacy Gemini CLI configuration.";
|
|
}
|
|
];
|
|
programs.antigravity-cli.settings.mcpServers =
|
|
lib.mkIf (cfg.enableMcpIntegration && config.programs.mcp.enable)
|
|
(
|
|
lib.mapAttrs (
|
|
name: server:
|
|
lib.hm.mcp.transformMcpServer {
|
|
inherit server;
|
|
extraTransforms = [
|
|
lib.hm.mcp.addType
|
|
(lib.hm.mcp.wrapEnvFilesCommand { inherit pkgs name; })
|
|
(s: lib.mapAttrs (_: lib.mkDefault) s)
|
|
];
|
|
}
|
|
) config.programs.mcp.servers
|
|
);
|
|
|
|
home.file =
|
|
lib.optionalAttrs (geminiSettings != { }) {
|
|
".gemini/settings.json".source = jsonFormat.generate "gemini-cli-settings.json" geminiSettings;
|
|
}
|
|
// lib.mapAttrs' (
|
|
n: v:
|
|
lib.nameValuePair ".gemini/commands/${n}.toml" {
|
|
source = tomlFormat.generate "gemini-cli-command-${n}.toml" {
|
|
inherit (v) description prompt;
|
|
};
|
|
}
|
|
) cfg.commands
|
|
// lib.mapAttrs' (
|
|
n: v:
|
|
lib.nameValuePair ".gemini/policies/${n}.toml" {
|
|
source =
|
|
if lib.hm.strings.isPathLike v then v else tomlFormat.generate "gemini-cli-policy-${n}.toml" v;
|
|
}
|
|
) cfg.policies
|
|
// skillFiles geminiSkillsDir;
|
|
})
|
|
(lib.mkIf (!useGeminiConfig) {
|
|
programs.antigravity-cli.mcpServers =
|
|
lib.mkIf (cfg.enableMcpIntegration && config.programs.mcp.enable)
|
|
(
|
|
lib.mapAttrs (
|
|
name: server:
|
|
lib.hm.mcp.transformMcpServer {
|
|
inherit server;
|
|
extraTransforms = [
|
|
lib.hm.mcp.addType
|
|
(lib.hm.mcp.wrapEnvFilesCommand { inherit pkgs name; })
|
|
(s: lib.mapAttrs (_: lib.mkDefault) s)
|
|
];
|
|
}
|
|
) config.programs.mcp.servers
|
|
);
|
|
|
|
assertions = [
|
|
{
|
|
assertion = cfg.policies == { };
|
|
message = ''
|
|
`programs.antigravity-cli.policies` is only supported when
|
|
`programs.antigravity-cli.package` is a `gemini-cli` package.
|
|
Antigravity CLI configures permissions in
|
|
`programs.antigravity-cli.settings.permissions` or
|
|
`programs.antigravity-cli.permissions`.
|
|
'';
|
|
}
|
|
];
|
|
|
|
home = {
|
|
activation = {
|
|
antigravitySettings = lib.mkIf (cfg.mutableSettings && nativeSettingsFiles != { }) (
|
|
lib.hm.dag.entryAfter [ "linkGeneration" ] (
|
|
lib.concatStringsSep "\n" (
|
|
lib.mapAttrsToList (
|
|
name: source:
|
|
lib.hm.generators.mkImpureConfigMerger {
|
|
inherit pkgs;
|
|
format = "json";
|
|
empty = "{}";
|
|
jqOperation = "$dynamic * $static";
|
|
path = "${config.home.homeDirectory}/${name}";
|
|
staticSettings = source;
|
|
mode = "600";
|
|
reader = if name == ".gemini/config/mcp_config.json" then "${lib.getExe json5} --as-json" else null;
|
|
}
|
|
) nativeSettingsFiles
|
|
)
|
|
)
|
|
);
|
|
antigravityImmutableSettings = lib.mkIf (!cfg.mutableSettings && nativeSettingsFiles != { }) (
|
|
lib.hm.dag.entryBetween [ "linkGeneration" ] [ "writeBoundary" ] (
|
|
lib.concatStringsSep "\n" (
|
|
lib.mapAttrsToList (
|
|
name: _: lib.hm.generators.mkImpureConfigCleanup { file = config.home.file.${name}; }
|
|
) nativeSettingsFiles
|
|
)
|
|
)
|
|
);
|
|
};
|
|
|
|
file =
|
|
lib.optionalAttrs (!cfg.mutableSettings) (
|
|
lib.mapAttrs (_: source: { inherit source; }) nativeSettingsFiles
|
|
)
|
|
// lib.mapAttrs' (
|
|
n: v:
|
|
let
|
|
skillName = commandSkillName n;
|
|
in
|
|
lib.nameValuePair "${antigravitySkillsDir}/${skillName}/SKILL.md" {
|
|
text = ''
|
|
---
|
|
name: ${skillName}
|
|
description: ${v.description}
|
|
---
|
|
|
|
${v.prompt}
|
|
'';
|
|
}
|
|
) cfg.commands
|
|
// skillFiles antigravitySkillsDir;
|
|
};
|
|
})
|
|
{
|
|
assertions = [
|
|
{
|
|
assertion = !lib.isPath cfg.skills || lib.pathIsDirectory cfg.skills;
|
|
message = "`programs.antigravity-cli.skills` must be a directory when set to a path";
|
|
}
|
|
];
|
|
}
|
|
]
|
|
);
|
|
}
|