agentleFS
Data-WiseData-Wise/craft/skills/distribution/homebrew-formula-expert/SKILL.md

homebrew-formula-expert

Skill4 starsChanged 13 days agoMITView on GitHub

This skill should be used when the user asks to "create a Homebrew formula", "fix brew audit errors", "write a formula", "Homebrew best practices", or needs help with Homebrew formula syntax, dependencies, and troubleshooting. Expert knowledge of Homebrew formula creation and maintenance.

  • Pipes a download into a shell
  • Deletes or force-pushes
  • Installs packages

The file

Files
SKILL.md

Homebrew Formula Expert

Deep expertise in Homebrew formula creation, maintenance, and best practices.

Surface scope: Homebrew installs plugins to the Claude Code CLI surface (~/.claude/). Claude Desktop (DXT/MCPB extensions — a different, MCP-server format) and Cowork are separate surfaces; see dist-extras and commands/dist/surfaces.md for the full model.

Formula Anatomy

class MyApp < Formula
  # Metadata
  desc "Short description (< 80 chars, no 'A' or 'An' prefix)"
  homepage "https://github.com/user/repo"
  url "https://github.com/user/repo/archive/v1.0.0.tar.gz"
  sha256 "64-char-hex-string"
  license "MIT"  # SPDX identifier
  head "https://github.com/user/repo.git", branch: "main"

  # Dependencies
  depends_on "python@3.12"
  depends_on "cmake" => :build  # Build-time only

  # Installation
  def install
    # Installation logic
  end

  # Verification
  test do
    # Test that installation worked
  end
end

Language-Specific Patterns

Python (virtualenv)

class MyPythonApp < Formula
  include Language::Python::Virtualenv

  desc "My Python application"
  homepage "https://github.com/user/repo"
  url "https://github.com/user/repo/archive/v1.0.0.tar.gz"
  sha256 "..."
  license "MIT"

  depends_on "python@3.12"

  # For packages with many deps, list resources
  resource "requests" do
    url "https://files.pythonhosted.org/..."
    sha256 "..."
  end

  def install
    virtualenv_install_with_resources
    # Or manual:
    # venv = virtualenv_create(libexec, "python3.12")
    # venv.pip_install resources
    # venv.pip_install buildpath
    # bin.install_symlink libexec/"bin/myapp"
  end

  test do
    assert_match version.to_s, shell_output("#{bin}/myapp --version")
  end
end

Node.js

class MyNodeApp < Formula
  desc "My Node.js application"
  homepage "https://github.com/user/repo"
  url "https://registry.npmjs.org/myapp/-/myapp-1.0.0.tgz"
  sha256 "..."
  license "MIT"

  depends_on "node"

  def install
    system "npm", "install", *std_npm_args
    bin.install_symlink Dir["#{libexec}/bin/*"]
  end

  test do
    assert_match version.to_s, shell_output("#{bin}/myapp --version")
  end
end

Go

class MyGoApp < Formula
  desc "My Go application"
  homepage "https://github.com/user/repo"
  url "https://github.com/user/repo/archive/v1.0.0.tar.gz"
  sha256 "..."
  license "MIT"

  depends_on "go" => :build

  def install
    system "go", "build", *std_go_args(ldflags: "-s -w -X main.version=#{version}")
  end

  test do
    assert_match version.to_s, shell_output("#{bin}/myapp --version")
  end
end

Rust

class MyRustApp < Formula
  desc "My Rust application"
  homepage "https://github.com/user/repo"
  url "https://github.com/user/repo/archive/v1.0.0.tar.gz"
  sha256 "..."
  license "MIT"

  depends_on "rust" => :build

  def install
    system "cargo", "install", *std_cargo_args
  end

  test do
    assert_match version.to_s, shell_output("#{bin}/myapp --version")
  end
end

Best Practices

Description (desc)

# Good
desc "Terminal optimizer for AI-assisted development"

# Bad
desc "A terminal optimizer for AI-assisted development"  # No 'A'
desc "aiterm is a terminal optimizer..."  # No app name
desc "..."  # Too long (> 80 chars)

URL Patterns

# GitHub release (preferred)
url "https://github.com/user/repo/archive/v1.0.0.tar.gz"

# GitHub release with refs/tags (more explicit)
url "https://github.com/user/repo/archive/refs/tags/v1.0.0.tar.gz"

# PyPI
url "https://files.pythonhosted.org/packages/.../myapp-1.0.0.tar.gz"

# npm
url "https://registry.npmjs.org/myapp/-/myapp-1.0.0.tgz"

SHA256 Calculation

# From URL
curl -sL https://github.com/user/repo/archive/v1.0.0.tar.gz | shasum -a 256

# From file
shasum -a 256 myapp-1.0.0.tar.gz

License (SPDX identifiers)

license "MIT"
license "Apache-2.0"
license "GPL-3.0-or-later"
license any_of: ["MIT", "Apache-2.0"]  # Dual license

Dependencies

# Runtime dependency
depends_on "python@3.12"

# Build-time only
depends_on "cmake" => :build

# Optional
depends_on "imagemagick" => :optional

# Recommended
depends_on "jq" => :recommended

# Platform-specific
depends_on "linux-headers" => :build if OS.linux?

Common Issues & Fixes

Issue: Python dependencies missing

# Problem: pip install fails due to missing deps

# Solution: Include as resources
resource "click" do
  url "https://files.pythonhosted.org/packages/.../click-8.0.0.tar.gz"
  sha256 "..."
end

# Or install individually
def install
  venv = virtualenv_create(libexec, "python3.12")
  venv.pip_install "click>=8.0"
  venv.pip_install "rich>=13.0"
  venv.pip_install buildpath
end

Issue: Binary not in PATH

# Solution: Create symlink
def install
  # ... installation ...
  bin.install_symlink libexec/"bin/myapp"
end

Issue: Test fails

# Problem: Command requires config file

# Solution: Create minimal config in test
test do
  (testpath/".config/myapp").mkpath
  (testpath/".config/myapp/config.json").write("{}")
  assert_match version.to_s, shell_output("#{bin}/myapp --version")
end

Issue: brew audit failures

# Run audit
brew audit --strict --online Formula/myapp.rb

# Common fixes:
# - desc: Remove 'A/An' prefix, keep < 80 chars
# - license: Use SPDX identifier
# - test: Must have test block
# - sha256: Must match URL content

Tap Management

Create Tap Repository

# Structure
homebrew-tap/
├── Formula/
│   ├── myapp.rb
│   └── othertool.rb
└── README.md

# Naming: homebrew-<tapname>
# Users install: brew tap user/tapname

Update Formula

# Old
url "https://github.com/user/repo/archive/v1.0.0.tar.gz"
sha256 "old-sha256"

# New
url "https://github.com/user/repo/archive/v1.0.1.tar.gz"
sha256 "new-sha256"

Testing Locally

# Install from local formula
brew install --build-from-source ./Formula/myapp.rb

# Reinstall after changes
brew reinstall --build-from-source ./Formula/myapp.rb

# Uninstall
brew uninstall myapp

Formula Template

class {{ClassName}} < Formula
  {{#if python}}
  include Language::Python::Virtualenv
  {{/if}}

  desc "{{description}}"
  homepage "{{homepage}}"
  url "{{url}}"
  sha256 "{{sha256}}"
  license "{{license}}"

  {{#each dependencies}}
  depends_on "{{this}}"
  {{/each}}

  def install
    {{install_commands}}
  end

  test do
    assert_match version.to_s, shell_output("#{bin}/{{binary}} --version")
  end
end

Claude Code Plugin Formula

For Claude Code plugins, use this special pattern with Claude detection:

class MyPlugin < Formula
  desc "My plugin - Claude Code plugin"
  homepage "https://github.com/user/my-plugin"
  url "https://github.com/user/my-plugin/archive/refs/tags/v1.0.0.tar.gz"
  sha256 "..."
  license "MIT"

  depends_on "jq" => :optional

  def install
    libexec.install Dir["*", ".*"].reject { |f| %w[. .. .git].include?(f) }

    (bin/"my-plugin-install").write <<~EOS
      #!/bin/bash
      PLUGIN_NAME="my-plugin"
      TARGET_DIR="$HOME/.claude/plugins/$PLUGIN_NAME"
      SOURCE_DIR="$(brew --prefix)/opt/my-plugin/libexec"

      echo "Installing plugin to Claude Code..."
      mkdir -p "$HOME/.claude/plugins" 2>/dev/null || true

      if [ -L "$TARGET_DIR" ] || [ -d "$TARGET_DIR" ]; then
          rm -rf "$TARGET_DIR" 2>/dev/null || true
      fi
      ln -sf "$SOURCE_DIR" "$TARGET_DIR"

      # Claude detection - skip auto-enable if Claude is running
      SETTINGS_FILE="$HOME/.claude/settings.json"
      AUTO_ENABLED=false
      CLAUDE_RUNNING=false

      if command -v lsof &>/dev/null; then
          if lsof "$SETTINGS_FILE" 2>/dev/null | grep -q "claude"; then
              CLAUDE_RUNNING=true
          fi
      elif pgrep -x "claude" >/dev/null 2>&1; then
          CLAUDE_RUNNING=true
      fi

      if [ "$CLAUDE_RUNNING" = false ] && command -v jq &>/dev/null && [ -f "$SETTINGS_FILE" ]; then
          TEMP_FILE=$(mktemp)
          if jq --arg plugin "${PLUGIN_NAME}@local-plugins" '.enabledPlugins[$plugin] = true' "$SETTINGS_FILE" > "$TEMP_FILE" 2>/dev/null; then
              mv "$TEMP_FILE" "$SETTINGS_FILE" 2>/dev/null && AUTO_ENABLED=true
          fi
          [ -f "$TEMP_FILE" ] && rm -f "$TEMP_FILE" 2>/dev/null
      fi

      echo "✅ Plugin installed!"
      if [ "$AUTO_ENABLED" = true ]; then
          echo "Plugin auto-enabled."
      elif [ "$CLAUDE_RUNNING" = true ]; then
          echo "Claude running - skipped auto-enable."
          echo "Run: claude plugin install ${PLUGIN_NAME}@local-plugins"
      else
          echo "Run: claude plugin install ${PLUGIN_NAME}@local-plugins"
      fi
    EOS

    chmod "+x", bin/"my-plugin-install"
  end

  def post_install
    system bin/"my-plugin-install"
    system "claude", "plugin", "update", "my-plugin@local-plugins" if which("claude")
  rescue
    nil
  end

  test do
    assert_predicate libexec/".claude-plugin/plugin.json", :exist?
  end
end

Why Claude Detection?

IssueWithout DetectionWith Detection
Claude runningHangs indefinitelySkips instantly
Install time2+ minutes2-3 seconds
User experienceConfusing hangClear message

Key Components

  1. lsof check - Detects if Claude has settings.json open
  2. pgrep fallback - Checks if claude process is running
  3. Skip logic - Bypasses settings.json modification if Claude running
  4. Clear messaging - Tells user exactly what to do

Integration

Use with:

  • /craft:dist:homebrew - Generate formula automatically
  • /craft:check --for release - Validate before formula update
  • /craft:code:release - Create version tag for formula

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.