- RFC-MULTI-AGENT-DEPLOYMENT.md: Design for unified deployment - modules/ai-skills.nix: Added geminiSkills option - bin/use-skills.sh: Added GEMINI_HOME support - bin/deploy-skill.sh: Inject configs for Codex and Gemini
254 lines
6.9 KiB
Bash
Executable file
254 lines
6.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
||
# Deploy a skill from this repo to dotfiles for system-wide availability
|
||
|
||
set -euo pipefail
|
||
|
||
SKILLS_REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
DOTFILES_REPO="$HOME/proj/dotfiles"
|
||
SKILL_NAME="${1:-}"
|
||
|
||
usage() {
|
||
cat <<EOF
|
||
Usage: $0 <skill-name>
|
||
|
||
Deploy a skill from ~/proj/skills to ~/proj/dotfiles for system-wide deployment.
|
||
|
||
Arguments:
|
||
skill-name Name of skill directory in skills/
|
||
|
||
Examples:
|
||
$0 screenshot-latest
|
||
$0 niri-window-capture
|
||
|
||
This script:
|
||
1. Copies skill to dotfiles/claude/skills/
|
||
2. Shows you the Nix config to add
|
||
3. Reminds you to rebuild
|
||
|
||
You must manually:
|
||
- Edit home/claude.nix
|
||
- Edit home/opencode.nix
|
||
- Run: sudo nixos-rebuild switch --flake .#delpad
|
||
- Restart AI agents
|
||
|
||
Available skills:
|
||
EOF
|
||
ls -1 "$SKILLS_REPO/skills" | grep -v template | sed 's/^/ /'
|
||
exit 1
|
||
}
|
||
|
||
# Function to inject config into Nix file
|
||
inject_nix_config() {
|
||
local target_file="$1"
|
||
local config_block="$2"
|
||
local marker="$3" # Unique string to check if already deployed
|
||
|
||
if [[ ! -f "$target_file" ]]; then
|
||
echo "⚠️ File not found: $target_file (skipping)"
|
||
return
|
||
fi
|
||
|
||
if grep -q "$marker" "$target_file"; then
|
||
echo "ℹ️ Config already present in $(basename "$target_file")"
|
||
else
|
||
echo "Injecting config into $(basename "$target_file")..."
|
||
|
||
# Create a secure temporary file
|
||
local temp_file
|
||
temp_file=$(mktemp "${target_file}.XXXXXX")
|
||
# Ensure cleanup on exit or error
|
||
trap 'rm -f "$temp_file"' EXIT
|
||
|
||
# Insert before the last line (assuming it is '}')
|
||
if ! head -n -1 "$target_file" > "$temp_file"; then
|
||
echo "Error: failed to read $target_file" >&2
|
||
return 1
|
||
fi
|
||
|
||
echo "$config_block" >> "$temp_file"
|
||
|
||
if ! tail -n 1 "$target_file" >> "$temp_file"; then
|
||
echo "Error: failed to append to $temp_file" >&2
|
||
return 1
|
||
fi
|
||
|
||
# Validate: temp file should be larger than original (since we're adding)
|
||
local orig_size
|
||
orig_size=$(stat -c%s "$target_file")
|
||
local new_size
|
||
new_size=$(stat -c%s "$temp_file")
|
||
|
||
if [[ $new_size -le $orig_size ]]; then
|
||
echo "Error: Validation failed, new file is not larger than original" >&2
|
||
return 1
|
||
fi
|
||
|
||
# Atomic move
|
||
if ! mv "$temp_file" "$target_file"; then
|
||
echo "Error: Failed to replace $target_file" >&2
|
||
return 1
|
||
fi
|
||
|
||
# Clear trap after successful move
|
||
trap - EXIT
|
||
echo "✓ Updated $(basename "$target_file")"
|
||
fi
|
||
}
|
||
|
||
# Helper to inject a home.file entry into a Nix config
|
||
# Usage: inject_home_file <target_nix_file> <dest_path_in_home> <source_relative_to_config> <extra_props> <comment>
|
||
inject_home_file() {
|
||
local target_file="$1"
|
||
local home_path="$2"
|
||
local source_path="$3"
|
||
local extra_props="$4"
|
||
local comment="$5"
|
||
|
||
local config_block="
|
||
# Skill: $comment
|
||
home.file.\"$home_path\" = {
|
||
source = $source_path;
|
||
$extra_props
|
||
};"
|
||
inject_nix_config "$target_file" "$config_block" "$home_path"
|
||
}
|
||
|
||
if [[ -z "$SKILL_NAME" ]]; then
|
||
usage
|
||
fi
|
||
|
||
SKILL_SOURCE="$SKILLS_REPO/skills/$SKILL_NAME"
|
||
SKILL_DEST="$DOTFILES_REPO/claude/skills/$SKILL_NAME"
|
||
|
||
# Validate skill exists
|
||
if [[ ! -d "$SKILL_SOURCE" ]]; then
|
||
echo "Error: Skill not found: $SKILL_SOURCE" >&2
|
||
echo "" >&2
|
||
usage
|
||
fi
|
||
|
||
# Validate dotfiles repo exists
|
||
if [[ ! -d "$DOTFILES_REPO" ]]; then
|
||
echo "Error: Dotfiles repo not found: $DOTFILES_REPO" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# Check if skill has SKILL.md
|
||
if [[ ! -f "$SKILL_SOURCE/SKILL.md" ]]; then
|
||
echo "Error: $SKILL_NAME missing SKILL.md" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# Check if already deployed
|
||
if [[ -d "$SKILL_DEST" ]]; then
|
||
echo "⚠️ Skill already deployed: $SKILL_DEST"
|
||
read -p "Overwrite? [y/N] " -n 1 -r
|
||
echo
|
||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||
echo "Cancelled"
|
||
exit 1
|
||
fi
|
||
rm -rf "$SKILL_DEST"
|
||
fi
|
||
|
||
# Check for security docs
|
||
SECURITY_WARNING=""
|
||
if [[ -f "$SKILL_SOURCE/SECURITY.md" ]]; then
|
||
SECURITY_WARNING="
|
||
⚠️ ⚠️ ⚠️ SECURITY WARNING ⚠️ ⚠️ ⚠️
|
||
|
||
This skill has security documentation.
|
||
READ BEFORE DEPLOYING: $SKILL_DEST/SECURITY.md
|
||
|
||
Security-sensitive skills should only be deployed after:
|
||
1. Reviewing security documentation
|
||
2. Understanding risks and mitigations
|
||
3. Configuring protection mechanisms
|
||
"
|
||
fi
|
||
|
||
echo "Deploying skill: $SKILL_NAME"
|
||
echo ""
|
||
echo "Source: $SKILL_SOURCE"
|
||
echo "Dest: $SKILL_DEST"
|
||
echo ""
|
||
|
||
# Copy skill
|
||
mkdir -p "$(dirname "$SKILL_DEST")"
|
||
cp -r "$SKILL_SOURCE" "$SKILL_DEST"
|
||
|
||
echo "✓ Skill copied to dotfiles"
|
||
echo ""
|
||
|
||
if [[ -n "$SECURITY_WARNING" ]]; then
|
||
echo "$SECURITY_WARNING"
|
||
fi
|
||
|
||
echo "Configuring system..."
|
||
echo ""
|
||
|
||
# 1. Claude Code Config
|
||
inject_home_file "$DOTFILES_REPO/home/claude.nix" \
|
||
".claude/skills/$SKILL_NAME" \
|
||
"../claude/skills/$SKILL_NAME" \
|
||
"recursive = true;" \
|
||
"$SKILL_NAME"
|
||
|
||
# 2. OpenCode Config
|
||
inject_home_file "$DOTFILES_REPO/home/opencode.nix" \
|
||
".config/opencode/skills/$SKILL_NAME" \
|
||
"../claude/skills/$SKILL_NAME" \
|
||
"recursive = true;" \
|
||
"$SKILL_NAME"
|
||
|
||
# 3. Codex Config (if home/codex.nix exists)
|
||
if [[ -f "$DOTFILES_REPO/home/codex.nix" ]]; then
|
||
inject_home_file "$DOTFILES_REPO/home/codex.nix" \
|
||
".codex/skills/$SKILL_NAME" \
|
||
"../claude/skills/$SKILL_NAME" \
|
||
"recursive = true;" \
|
||
"$SKILL_NAME"
|
||
fi
|
||
|
||
# 4. Gemini Config (if home/gemini.nix exists)
|
||
if [[ -f "$DOTFILES_REPO/home/gemini.nix" ]]; then
|
||
inject_home_file "$DOTFILES_REPO/home/gemini.nix" \
|
||
".gemini/skills/$SKILL_NAME" \
|
||
"../claude/skills/$SKILL_NAME" \
|
||
"recursive = true;" \
|
||
"$SKILL_NAME"
|
||
fi
|
||
|
||
# 5. Antigravity / Global Config
|
||
# Check if antigravity.nix exists, otherwise warn
|
||
ANTIGRAVITY_NIX="$DOTFILES_REPO/home/antigravity.nix"
|
||
if [[ -f "$ANTIGRAVITY_NIX" ]]; then
|
||
# For global scripts, we need to find executable scripts in the skill
|
||
if [[ -d "$SKILL_SOURCE/scripts" ]]; then
|
||
SCRIPTS=$(find "$SKILL_SOURCE/scripts" -name "*.sh" -type f)
|
||
|
||
for script in $SCRIPTS; do
|
||
SCRIPT_NAME=$(basename "$script")
|
||
SCRIPT_NO_EXT="${SCRIPT_NAME%.*}"
|
||
LINK_NAME="$SCRIPT_NO_EXT"
|
||
|
||
inject_home_file "$ANTIGRAVITY_NIX" \
|
||
".local/bin/$LINK_NAME" \
|
||
"../claude/skills/$SKILL_NAME/scripts/$SCRIPT_NAME" \
|
||
"executable = true;" \
|
||
"$SKILL_NAME ($SCRIPT_NAME)"
|
||
done
|
||
fi
|
||
else
|
||
echo "⚠️ $ANTIGRAVITY_NIX not found. Skipping global binary configuration."
|
||
echo " To enable global binaries, create home/antigravity.nix and add it to your flake."
|
||
fi
|
||
|
||
echo ""
|
||
echo "Deployment configured."
|
||
echo "Run the following to apply changes:"
|
||
echo ""
|
||
echo " cd $DOTFILES_REPO"
|
||
echo " sudo nixos-rebuild switch --flake .#delpad"
|
||
echo ""
|
||
echo "Then restart your agents." |