# 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: ```yaml 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. > [!info] 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 `~/.claude/skills/*/` and parses the metadata JSON from each SKILL.md: ```bash 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 `~/scripts/skills-inventory.json`: ```json { "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. > [!tip] 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` ```bash for formula in "${BREW_FORMULAS[@]}"; do formula_short="${formula##*/}" if brew outdated --quiet | grep -q "^${formula_short}quot;; then brew upgrade "$formula_short" fi done ``` **Go**: Reinstall with `@latest` (Go modules are idempotent) ```bash for module in "${GO_MODULES[@]}"; do bin_name="${module##*/}" bin_name="${bin_name%@*}" go install "$module" done ``` **Node**: Use `npm update -g` ```bash for package in "${NODE_PACKAGES[@]}"; do npm update -g "$package" done ``` ## Scheduling with LaunchAgent The script runs weekly via macOS LaunchAgent. Create `~/Library/LaunchAgents/com.bioinfo.skills-updater.plist`: ```xml <?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: ```bash launchctl load ~/Library/LaunchAgents/com.bioinfo.skills-updater.plist ``` Now every Sunday at 4 AM, the script runs automatically. All updates get logged to `~/logs/skills-updater.log`. ## 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. > [!warning] 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 ```bash # Run manually ~/scripts/skills-updater.sh # Dry run (see what would update) ~/scripts/skills-updater.sh --dry-run # Check LaunchAgent status launchctl list | grep skills-updater # View last run tail -100 ~/logs/skills-updater.log # View inventory cat ~/scripts/skills-inventory.json | 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 - [[Practical Applications/claude-skills-vs-mcp-servers|Claude Skills vs MCP Servers]] - [[AI Development & Agents/claude-code-best-practices|Claude Code Best Practices]] - [[Practical Applications/debugging-claude-code-with-claude|Debugging Claude Code with Claude]] --- <p style="text-align: center;"><strong>About the Author</strong>: Justin Johnson builds AI systems and writes about practical AI development.</p> <p style="text-align: center;"><a href="https://justinhjohnson.com">justinhjohnson.com</a> | <a href="https://twitter.com/bioinfo">Twitter</a> | <a href="https://www.linkedin.com/in/justinhaywardjohnson/">LinkedIn</a> | <a href="https://rundatarun.io">Run Data Run</a> | <a href="https://subscribe.rundatarun.io">Subscribe</a></p>