Practical Applications8 min readshipped

Auto-Updating CLI Tools for Claude Code Skills

Auto-Updating CLI Tools for Claude Code Skills

Claude Code skills often depend on external CLI tools. The bird skill needs the bird CLI for Twitter. The gifgrep skill needs gifgrep for searching GIFs. The obsidian skill needs obsidian-cli. These tools ship updates regularly, and falling behind means missing bug fixes, new features, and security patches.

I just saw bird 0.7.0 drop with home timeline support and a bunch of fixes. My system was running 0.6.0. That got me thinking: how many other CLI tools are silently outdated across my 74 skills?

The answer: build a system that automatically discovers what CLI tools my skills need and keeps them updated.

The Problem: Hidden Dependencies

Claude Code skills declare their CLI dependencies in SKILL.md metadata. Here's what the bird skill looks like:

metadata: {"clawdbot":{"emoji":"🐦","requires":{"bins":["bird"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/bird","bins":["bird"],"label":"Install bird (brew)"}]}}

This structured metadata tells us:

  • The skill needs bird binary
  • It can be installed via Homebrew from steipete/tap/bird

The same pattern works for Go modules and Node packages. But manually checking 74 skills for outdated dependencies? Not happening.

Why This Matters for Agents

When Claude Code invokes a skill that shells out to a CLI tool, outdated versions can cause silent failures. The agent doesn't know why bird read suddenly returns different JSON. Keeping tools current prevents debugging sessions that waste both your time and tokens.

The Solution: Metadata-Driven Discovery

The update system works in three phases:

  1. Discovery: Parse all SKILL.md files, extract install metadata
  2. Inventory: Build a JSON registry of all CLI dependencies
  3. Update: Check each source (Homebrew, Go, Node) and apply updates

Phase 1: Discovering CLI Dependencies

The script iterates through `(local path) and parses the metadata JSON from each SKILL.md:

for skill_dir in "$SKILLS_DIR"/*/; do
    skill_file="${skill_dir}SKILL.md"
    -f "$skill_file" || continue

    # Extract metadata JSON line
    metadata_line=$(grep -E "^metadata:" "$skill_file" | head -1 | sed 's/^metadata: //')
    -z "$metadata_line" && continue

    # Parse install entries using jq
    while IFS= read -r line; do
        kind=$(echo "$line" | cut -d'|' -f1)
        value=$(echo "$line" | cut -d'|' -f2)

        case "$kind" in
            brew) BREW_FORMULAS+=("$value") ;;
            go)   GO_MODULES+=("$value") ;;
            node) NODE_PACKAGES+=("$value") ;;
        esac
    done < <(echo "$metadata_line" | jq -r '.clawdbot.install[]? | "\(.kind)|\(.formula // .module // .package)"')
done

This discovered 34 CLI tools across my 73 skills:

  • 21 Homebrew formulas
  • 10 Go modules
  • 3 Node packages

Phase 2: Building the Inventory

The script generates a JSON inventory at `(local path)

{
  "generated": "2026-01-12T11:19:01-05:00",
  "skills_scanned": 73,
  "cli_tools": 34,
  "brew_formulas": [
    "steipete/tap/bird",
    "steipete/tap/gifgrep",
    "steipete/tap/summarize",
    "yakitrak/yakitrak/obsidian-cli"
  ],
  "go_modules": [
    "github.com/steipete/blucli/cmd/blu@latest",
    "github.com/Hyaxia/blogwatcher/cmd/blogwatcher@latest"
  ],
  "node_packages": [
    "clawdhub",
    "@steipete/oracle"
  ]
}

This inventory serves two purposes: tracking what needs updating and documenting what CLI ecosystem the skills depend on.

Version Pinning Decision

I chose not to implement version pinning. These are dev tools, not production dependencies. Weekly updates give enough time to catch issues before they compound. If a specific tool causes problems, pinning can be added later.

Phase 3: Applying Updates

Each package manager gets handled differently:

Homebrew: Check brew outdated, then brew upgrade

for formula in "${BREW_FORMULAS[@]}"; do
    formula_short="${formula##*/}"

    if brew outdated --quiet | grep -q "^${formula_short}$"; then
        brew upgrade "$formula_short"
    fi
done

Go: Reinstall with @latest (Go modules are idempotent)

for module in "${GO_MODULES[@]}"; do
    bin_name="${module##*/}"
    bin_name="${bin_name%@*}"

    go install "$module"
done

Node: Use npm update -g

for package in "${NODE_PACKAGES[@]}"; do
    npm update -g "$package"
done

Scheduling with LaunchAgent

The script runs weekly via macOS LaunchAgent. Create `(local path)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.bioinfo.skills-updater</string>

    <key>ProgramArguments</key>
    <array>
        <string>/Users/bioinfo/scripts/skills-updater.sh</string>
        <string>--quiet</string>
    </array>

    <key>StartCalendarInterval</key>
    <dict>
        <key>Weekday</key>
        <integer>0</integer>
        <key>Hour</key>
        <integer>4</integer>
    </dict>

    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>/opt/homebrew/bin:/usr/local/go/bin:...</string>
    </dict>
</dict>
</plist>

Load it with:

launchctl load (local path)

Now every Sunday at 4 AM, the script runs automatically. All updates get logged to `(local path)

Running It: First Test Results

[2026-01-12 11:19:01] Skills CLI Updater - 2026-01-12 11:19
[2026-01-12 11:19:01] Phase 1: Discovering CLI dependencies from skills...
[2026-01-12 11:19:01] Discovered 34 CLI tools across 73 skills
[2026-01-12 11:19:01]   Homebrew: 21 formulas
[2026-01-12 11:19:01]   Go: 10 modules
[2026-01-12 11:19:01]   Node: 3 packages
...
[2026-01-12 11:19:18]   bird: 0.6.0 -> 0.7.0 (updating...)
[2026-01-12 11:19:19]     SUCCESS: bird updated

Bird went from 0.6.0 to 0.7.0. The new version includes home timeline support, better pagination, and a bunch of bug fixes. All without me having to remember to check.

Tap Freshness

Homebrew taps don't always reflect new versions immediately. The script runs brew update first, but third-party taps may lag. If you know a new version exists and the script doesn't see it, run brew tap --repair <tap-name> manually.

The Practical Benefits

Reduced debugging time. When a skill fails, outdated CLI tools are one less thing to check. You know they're current.

Security patches applied automatically. CLI tools get vulnerability fixes too. Weekly updates mean you're never more than 7 days behind.

New features available immediately. Bird 0.7.0 added home timeline support. Now the bird skill can use it without manual intervention.

Discoverable dependencies. The inventory JSON documents exactly what CLI ecosystem your skills depend on. Useful for documentation, auditing, or setting up a new machine.

Manual Commands

# Run manually
(local path)

# Dry run (see what would update)
(local path) --dry-run

# Check LaunchAgent status
launchctl list | grep skills-updater

# View last run
tail -100 (local path)

# View inventory
cat (local path) | jq

What's Next

This handles the happy path. Future improvements could include:

  • Slack/Discord notifications when updates are applied
  • Rollback support if an update breaks something
  • Version constraint parsing for skills that need specific versions
  • Cross-machine sync for homelab setups with multiple nodes

For now, the system does what I need: keeps 34 CLI tools current across 73 skills without me thinking about it.

The code is straightforward bash. If you're running Claude Code with skills that depend on CLI tools, adapt it to your setup. The core insight is that skill metadata already contains the dependency information. You just need to parse it.


Related Articles

  • Claude Skills vs MCP Servers
  • Claude Code Best Practices
  • Debugging Claude Code with Claude

About the Author: Justin Johnson builds AI systems and writes about practical AI development.

justinhjohnson.com | Twitter | LinkedIn | Run Data Run | Subscribe

Follow the lab

Get the next experiment

Enjoyed the breakdown on Auto-Updating CLI Tools for Claude Code Skills? New entries land roughly weekly. No digest, no roundup. Just the next build log, when it ships.

Links to this entry