Back to skills
SKILL.md
2202 2026 02 03 Bird Cli Implementation 88ab477b
ASecurity> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Add Bird CLI as a free, zero-config alternative to xAI for X/Twitter searches with interactive installation. **Architecture:** New `bird_x.py` module handles Bird detection, installation prompts, and search. Modified `env.py` determines X source priority (Bird → xAI → WebSearch). Main script prompts for Bird install if not found. **Tech Stack:** Python 3, subprocess for Bird CL...
- 9 stars
- 0 votes
- 0 copies
- 1 view
- Added October 11, 2026
Works with
Security analysis
100/100npx -y skills add tools-only/X-Skills --skill 2202-2026-02-03-bird-cli-implementation_88ab477b --agent claude-codeAre you the author of 2202 2026 02 03 Bird Cli Implementation 88ab477b?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/tools-only-2202-2026-02-03-bird-cli-implementation-88ab477b)# Bird CLI Integration Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Add Bird CLI as a free, zero-config alternative to xAI for X/Twitter searches with interactive installation.
**Architecture:** New `bird_x.py` module handles Bird detection, installation prompts, and search. Modified `env.py` determines X source priority (Bird → xAI → WebSearch). Main script prompts for Bird install if not found.
**Tech Stack:** Python 3, subprocess for Bird CLI calls, existing lib modules for normalization/scoring.
---
### Task 1: Create bird_x.py - Detection Functions
**Files:**
- Create: `scripts/lib/bird_x.py`
**Step 1: Create the module with detection functions**
```python
"""Bird CLI client for X (Twitter) search."""
import json
import shutil
import subprocess
import sys
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
def _log(msg: str):
"""Log to stderr."""
sys.stderr.write(f"[Bird] {msg}\n")
sys.stderr.flush()
def is_bird_installed() -> bool:
"""Check if Bird CLI is installed."""
return shutil.which("bird") is not None
def is_bird_authenticated() -> Optional[str]:
"""Check if Bird is authenticated by running 'bird whoami'.
Returns:
Username if authenticated, None otherwise.
"""
if not is_bird_installed():
return None
try:
result = subprocess.run(
["bird", "whoami"],
capture_output=True,
text=True,
timeout=10,
)
if result.returncode == 0 and result.stdout.strip():
# Output is typically the username
return result.stdout.strip().split('\n')[0]
return None
except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
return None
def check_npm_available() -> bool:
"""Check if npm is available for installation."""
return shutil.which("npm") is not None
```
**Step 2: Verify the module loads**
Run: `cd /Users/mvanhorn/last30days-skill-private && python3 -c "from scripts.lib import bird_x; print('OK')"`
Expected: `OK`
**Step 3: Commit**
```bash
git add scripts/lib/bird_x.py
git commit -m "feat(bird): add detection functions for Bird CLI"
```
---
### Task 2: Add Bird Installation Functions
**Files:**
- Modify: `scripts/lib/bird_x.py`
**Step 1: Add installation function**
Add after `check_npm_available()`:
```python
def install_bird() -> Tuple[bool, str]:
"""Install Bird CLI via npm.
Returns:
Tuple of (success, message).
"""
if not check_npm_available():
return False, "npm not found. Install Node.js first, or install Bird manually: https://github.com/steipete/bird"
try:
_log("Installing Bird CLI...")
result = subprocess.run(
["npm", "install", "-g", "@steipete/bird"],
capture_output=True,
text=True,
timeout=120,
)
if result.returncode == 0:
return True, "Bird CLI installed successfully!"
else:
error = result.stderr.strip() or result.stdout.strip() or "Unknown error"
return False, f"Installation failed: {error}"
except subprocess.TimeoutExpired:
return False, "Installation timed out"
except Exception as e:
return False, f"Installation error: {e}"
def get_bird_status() -> Dict[str, Any]:
"""Get comprehensive Bird status.
Returns:
Dict with keys: installed, authenticated, username, can_install
"""
installed = is_bird_installed()
username = is_bird_authenticated() if installed else None
return {
"installed": installed,
"authenticated": username is not None,
"username": username,
"can_install": check_npm_available(),
}
```
**Step 2: Verify functions work**
Run: `cd /Users/mvanhorn/last30days-skill-private && python3 -c "from scripts.lib import bird_x; print(bird_x.get_bird_status())"`
Expected: Dict with installed/authenticated status
**Step 3: Commit**
```bash
git add scripts/lib/bird_x.py
git commit -m "feat(bird): add installation and status functions"
```
---
### Task 3: Add Bird Search Function
**Files:**
- Modify: `scripts/lib/bird_x.py`
**Step 1: Add depth config and search function**
Add after imports at top:
```python
# Depth configurations: number of results to request
DEPTH_CONFIG = {
"quick": 12,
"default": 30,
"deep": 60,
}
```
Add after `get_bird_status()`:
```python
def search_x(
topic: str,
from_date: str,
to_date: str,
depth: str = "default",
) -> Dict[str, Any]:
"""Search X using Bird CLI.
Args:
topic: Search topic
from_date: Start date (YYYY-MM-DD)
to_date: End date (YYYY-MM-DD)
depth: Research depth - "quick", "default", or "deep"
Returns:
Raw Bird JSON response or error dict.
"""
count = DEPTH_CONFIG.get(depth, DEPTH_CONFIG["default"])
# Build command
cmd = [
"bird", "search",
topic,
"--since", from_date,
"-n", str(count),
"--json",
]
# Adjust timeout based on depth
timeout = 30 if depth == "quick" else 45 if depth == "default" else 60
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout,
)
if result.returncode != 0:
error = result.stderr.strip() or "Bird search failed"
return {"error": error, "items": []}
# Parse JSON output
output = result.stdout.strip()
if not output:
return {"items": []}
return json.loads(output)
except subprocess.TimeoutExpired:
return {"error": "Search timed out", "items": []}
except json.JSONDecodeError as e:
return {"error": f"Invalid JSON response: {e}", "items": []}
except Exception as e:
return {"error": str(e), "items": []}
```
**Step 2: Verify search function signature**
Run: `cd /Users/mvanhorn/last30days-skill-private && python3 -c "from scripts.lib import bird_x; import inspect; print(inspect.signature(bird_x.search_x))"`
Expected: `(topic: str, from_date: str, to_date: str, depth: str = 'default') -> Dict[str, Any]`
**Step 3: Commit**
```bash
git add scripts/lib/bird_x.py
git commit -m "feat(bird): add search_x function"
```
---
### Task 4: Add Bird Response Parser
**Files:**
- Modify: `scripts/lib/bird_x.py`
**Step 1: Add parse function**
Add at end of file:
```python
def parse_bird_response(response: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Parse Bird response to match xai_x output format.
Args:
response: Raw Bird JSON response
Returns:
List of normalized item dicts matching xai_x.parse_x_response() format.
"""
items = []
# Check for errors
if "error" in response and response["error"]:
_log(f"Bird error: {response['error']}")
return items
# Bird returns a list of tweets directly or under a key
raw_items = response if isinstance(response, list) else response.get("items", response.get("tweets", []))
if not isinstance(raw_items, list):
return items
for i, tweet in enumerate(raw_items):
if not isinstance(tweet, dict):
continue
# Extract URL - Bird uses permanent_url or we construct from id
url = tweet.get("permanent_url") or tweet.get("url", "")
if not url and tweet.get("id"):
screen_name = tweet.get("user", {}).get("screen_name", "")
if screen_name:
url = f"https://x.com/{screen_name}/status/{tweet['id']}"
if not url:
continue
# Parse date from created_at (e.g., "Wed Jan 15 14:30:00 +0000 2026")
date = None
created_at = tweet.get("created_at", "")
if created_at:
try:
# Try ISO format first
if "T" in created_at:
dt = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
else:
# Twitter format: "Wed Jan 15 14:30:00 +0000 2026"
dt = datetime.strptime(created_at, "%a %b %d %H:%M:%S %z %Y")
date = dt.strftime("%Y-%m-%d")
except (ValueError, TypeError):
pass
# Extract user info
user = tweet.get("user", {})
author_handle = user.get("screen_name", "") or tweet.get("author_handle", "")
# Build engagement dict
engagement = {
"likes": tweet.get("like_count") or tweet.get("favorite_count"),
"reposts": tweet.get("retweet_count"),
"replies": tweet.get("reply_count"),
"quotes": tweet.get("quote_count"),
}
# Convert to int where possible
for key in engagement:
if engagement[key] is not None:
try:
engagement[key] = int(engagement[key])
except (ValueError, TypeError):
engagement[key] = None
# Build normalized item
item = {
"id": f"X{i+1}",
"text": str(tweet.get("text", tweet.get("full_text", ""))).strip()[:500],
"url": url,
"author_handle": author_handle.lstrip("@"),
"date": date,
"engagement": engagement if any(v is not None for v in engagement.values()) else None,
"why_relevant": "", # Bird doesn't provide relevance explanations
"relevance": 0.7, # Default relevance, let score.py re-rank
}
items.append(item)
return items
```
**Step 2: Verify parser handles empty input**
Run: `cd /Users/mvanhorn/last30days-skill-private && python3 -c "from scripts.lib import bird_x; print(bird_x.parse_bird_response({}))"`
Expected: `[]`
**Step 3: Commit**
```bash
git add scripts/lib/bird_x.py
git commit -m "feat(bird): add response parser matching xai_x format"
```
---
### Task 5: Add UI Functions for Bird Prompts
**Files:**
- Modify: `scripts/lib/ui.py`
**Step 1: Add Bird-related messages and prompts**
Add after `PROMO_SINGLE_KEY_PLAIN` dict (around line 128):
```python
# Bird CLI prompts
BIRD_INSTALL_PROMPT = f"""
{Colors.CYAN}{Colors.BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.RESET}
{Colors.CYAN}🐦 FREE X/TWITTER SEARCH AVAILABLE{Colors.RESET}
Bird CLI provides free X search using your browser session (no API key needed).
"""
BIRD_INSTALL_PROMPT_PLAIN = """
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🐦 FREE X/TWITTER SEARCH AVAILABLE
Bird CLI provides free X search using your browser session (no API key needed).
"""
BIRD_AUTH_HELP = f"""
{Colors.YELLOW}Bird authentication failed.{Colors.RESET}
To fix this:
1. Log into X (twitter.com) in Safari, Chrome, or Firefox
2. Run: {Colors.BOLD}bird check{Colors.RESET} to verify credentials
3. Try again
For manual setup, see: https://github.com/steipete/bird#authentication
"""
BIRD_AUTH_HELP_PLAIN = """
Bird authentication failed.
To fix this:
1. Log into X (twitter.com) in Safari, Chrome, or Firefox
2. Run: bird check to verify credentials
3. Try again
For manual setup, see: https://github.com/steipete/bird#authentication
"""
```
**Step 2: Add prompt functions to ProgressDisplay class**
Add these methods to the `ProgressDisplay` class (after `show_promo` method, around line 310):
```python
def prompt_bird_install(self) -> bool:
"""Prompt user to install Bird CLI.
Returns:
True if user wants to install, False otherwise.
"""
if IS_TTY:
sys.stderr.write(BIRD_INSTALL_PROMPT)
else:
sys.stderr.write(BIRD_INSTALL_PROMPT_PLAIN)
sys.stderr.flush()
try:
response = input("Install Bird CLI now? (y/n): ").strip().lower()
return response in ('y', 'yes')
except (EOFError, KeyboardInterrupt):
return False
def show_bird_install_success(self, username: str):
"""Show Bird installation success message."""
msg = f"{Colors.GREEN}✓ Bird installed and authenticated as @{username}{Colors.RESET}\n" if IS_TTY else f"✓ Bird installed and authenticated as @{username}\n"
sys.stderr.write(msg)
sys.stderr.flush()
def show_bird_install_failed(self, error: str):
"""Show Bird installation failure message."""
msg = f"{Colors.RED}✗ Bird installation failed: {error}{Colors.RESET}\n" if IS_TTY else f"✗ Bird installation failed: {error}\n"
sys.stderr.write(msg)
sys.stderr.flush()
def show_bird_auth_help(self):
"""Show Bird authentication help."""
if IS_TTY:
sys.stderr.write(BIRD_AUTH_HELP)
else:
sys.stderr.write(BIRD_AUTH_HELP_PLAIN)
sys.stderr.flush()
```
**Step 3: Verify new methods exist**
Run: `cd /Users/mvanhorn/last30days-skill-private && python3 -c "from scripts.lib.ui import ProgressDisplay; p = ProgressDisplay('test', show_banner=False); print(hasattr(p, 'prompt_bird_install'))"`
Expected: `True`
**Step 4: Commit**
```bash
git add scripts/lib/ui.py
git commit -m "feat(ui): add Bird CLI install prompts and auth help"
```
---
### Task 6: Update env.py with X Source Detection
**Files:**
- Modify: `scripts/lib/env.py`
**Step 1: Add get_x_source function**
Add at end of file:
```python
def get_x_source(config: Dict[str, Any]) -> Optional[str]:
"""Determine the best available X/Twitter source.
Priority: Bird (free) → xAI (paid API)
Args:
config: Configuration dict from get_config()
Returns:
'bird' if Bird is installed and authenticated,
'xai' if XAI_API_KEY is configured,
None if no X source available.
"""
# Import here to avoid circular dependency
from . import bird_x
# Check Bird first (free option)
if bird_x.is_bird_installed():
username = bird_x.is_bird_authenticated()
if username:
return 'bird'
# Fall back to xAI if key exists
if config.get('XAI_API_KEY'):
return 'xai'
return None
def get_x_source_status(config: Dict[str, Any]) -> Dict[str, Any]:
"""Get detailed X source status for UI decisions.
Returns:
Dict with keys: source, bird_installed, bird_authenticated,
bird_username, xai_available, can_install_bird
"""
from . import bird_x
bird_status = bird_x.get_bird_status()
xai_available = bool(config.get('XAI_API_KEY'))
# Determine active source
if bird_status["authenticated"]:
source = 'bird'
elif xai_available:
source = 'xai'
else:
source = None
return {
"source": source,
"bird_installed": bird_status["installed"],
"bird_authenticated": bird_status["authenticated"],
"bird_username": bird_status["username"],
"xai_available": xai_available,
"can_install_bird": bird_status["can_install"],
}
```
**Step 2: Verify function works**
Run: `cd /Users/mvanhorn/last30days-skill-private && python3 -c "from scripts.lib import env; print(env.get_x_source_status(env.get_config()))"`
Expected: Dict with source status
**Step 3: Commit**
```bash
git add scripts/lib/env.py
git commit -m "feat(env): add X source detection with Bird priority"
```
---
### Task 7: Update __init__.py to Export bird_x
**Files:**
- Modify: `scripts/lib/__init__.py`
**Step 1: Add bird_x to imports**
Replace file contents with:
```python
# last30days library modules
from . import bird_x
```
**Step 2: Verify import works**
Run: `cd /Users/mvanhorn/last30days-skill-private && python3 -c "from scripts.lib import bird_x; print('OK')"`
Expected: `OK`
**Step 3: Commit**
```bash
git add scripts/lib/__init__.py
git commit -m "feat(lib): export bird_x module"
```
---
### Task 8: Integrate Bird into Main Script - Part 1 (Setup Phase)
**Files:**
- Modify: `scripts/last30days.py`
**Step 1: Add bird_x import**
Add `bird_x` to the imports from lib (around line 36):
```python
from lib import (
bird_x,
dates,
dedupe,
env,
http,
models,
normalize,
openai_reddit,
reddit_enrich,
render,
schema,
score,
ui,
websearch,
xai_x,
)
```
**Step 2: Add Bird setup function**
Add after the imports, before `load_fixture`:
```python
def setup_bird_if_needed(progress: ui.ProgressDisplay) -> Optional[str]:
"""Check Bird status and offer installation if needed.
Returns:
'bird' if Bird is ready to use,
'declined' if user declined install,
None if Bird not available and couldn't be installed.
"""
status = bird_x.get_bird_status()
# Already working
if status["authenticated"]:
return 'bird'
# Installed but not authenticated
if status["installed"]:
progress.show_bird_auth_help()
return None
# Not installed - offer to install if npm available
if status["can_install"]:
if progress.prompt_bird_install():
success, message = bird_x.install_bird()
if success:
# Check if auth works now
username = bird_x.is_bird_authenticated()
if username:
progress.show_bird_install_success(username)
return 'bird'
else:
progress.show_bird_auth_help()
return None
else:
progress.show_bird_install_failed(message)
return None
else:
return 'declined'
return None
```
**Step 3: Verify script still loads**
Run: `cd /Users/mvanhorn/last30days-skill-private && python3 -c "import scripts.last30days; print('OK')"`
Expected: `OK`
**Step 4: Commit**
```bash
git add scripts/last30days.py
git commit -m "feat(main): add Bird setup function"
```
---
### Task 9: Integrate Bird into Main Script - Part 2 (Search Dispatch)
**Files:**
- Modify: `scripts/last30days.py`
**Step 1: Modify _search_x function to support Bird**
Replace the `_search_x` function (around line 119-159) with:
```python
def _search_x(
topic: str,
config: dict,
selected_models: dict,
from_date: str,
to_date: str,
depth: str,
mock: bool,
x_source: str = "xai",
) -> tuple:
"""Search X via Bird CLI or xAI (runs in thread).
Args:
x_source: 'bird' or 'xai' - which backend to use
Returns:
Tuple of (x_items, raw_response, error)
"""
raw_response = None
x_error = None
if mock:
raw_response = load_fixture("xai_sample.json")
x_items = xai_x.parse_x_response(raw_response or {})
return x_items, raw_response, x_error
# Use Bird if specified
if x_source == "bird":
try:
raw_response = bird_x.search_x(
topic,
from_date,
to_date,
depth=depth,
)
except Exception as e:
raw_response = {"error": str(e)}
x_error = f"{type(e).__name__}: {e}"
x_items = bird_x.parse_bird_response(raw_response or {})
# Check for error in response
if raw_response and raw_response.get("error") and not x_error:
x_error = raw_response["error"]
return x_items, raw_response, x_error
# Use xAI (original behavior)
try:
raw_response = xai_x.search_x(
config["XAI_API_KEY"],
selected_models["xai"],
topic,
from_date,
to_date,
depth=depth,
)
except http.HTTPError as e:
raw_response = {"error": str(e)}
x_error = f"API error: {e}"
except Exception as e:
raw_response = {"error": str(e)}
x_error = f"{type(e).__name__}: {e}"
x_items = xai_x.parse_x_response(raw_response or {})
return x_items, raw_response, x_error
```
**Step 2: Update run_research to accept x_source parameter**
Find the `run_research` function signature (around line 161) and add `x_source` parameter:
```python
def run_research(
topic: str,
sources: str,
config: dict,
selected_models: dict,
from_date: str,
to_date: str,
depth: str = "default",
mock: bool = False,
progress: ui.ProgressDisplay = None,
x_source: str = "xai",
) -> tuple:
```
Then update the `_search_x` call inside (around line 218-222) to pass `x_source`:
```python
x_future = executor.submit(
_search_x, topic, config, selected_models,
from_date, to_date, depth, mock, x_source
)
```
**Step 3: Verify script syntax is valid**
Run: `cd /Users/mvanhorn/last30days-skill-private && python3 -m py_compile scripts/last30days.py && echo "OK"`
Expected: `OK`
**Step 4: Commit**
```bash
git add scripts/last30days.py
git commit -m "feat(main): dispatch X search to Bird or xAI"
```
---
### Task 10: Integrate Bird into Main Script - Part 3 (Main Function)
**Files:**
- Modify: `scripts/last30days.py`
**Step 1: Update main() to check Bird before research**
In the `main()` function, after loading config and before checking available sources (around line 345-355), add Bird setup:
Find this section:
```python
# Load config
config = env.get_config()
# Check available sources
available = env.get_available_sources(config)
```
Replace with:
```python
# Load config
config = env.get_config()
# Initialize progress display early for Bird prompts
progress = ui.ProgressDisplay(args.topic, show_banner=True)
# Check Bird availability and offer install if needed
x_source_status = env.get_x_source_status(config)
x_source = x_source_status["source"]
# If no X source and Bird can be installed, offer it
if x_source is None and x_source_status["can_install_bird"]:
bird_result = setup_bird_if_needed(progress)
if bird_result == 'bird':
x_source = 'bird'
# Refresh status
x_source_status = env.get_x_source_status(config)
# Check available sources (now accounting for Bird)
available = env.get_available_sources(config)
# Override available if Bird is ready
if x_source == 'bird':
if available == 'reddit':
available = 'both' # Now have both Reddit + X (via Bird)
elif available == 'web':
available = 'x' # Now have X via Bird
```
**Step 2: Remove duplicate progress initialization**
Find and remove the later `progress = ui.ProgressDisplay(...)` line (around line 371) since we now create it earlier.
**Step 3: Pass x_source to run_research**
Find the `run_research` call (around line 413) and add `x_source` parameter:
```python
reddit_items, x_items, web_needed, raw_openai, raw_xai, raw_reddit_enriched, reddit_error, x_error = run_research(
args.topic,
sources,
config,
selected_models,
from_date,
to_date,
depth,
args.mock,
progress,
x_source=x_source or "xai",
)
```
**Step 4: Verify script runs with --help**
Run: `cd /Users/mvanhorn/last30days-skill-private && python3 scripts/last30days.py --help`
Expected: Help text displays without errors
**Step 5: Commit**
```bash
git add scripts/last30days.py
git commit -m "feat(main): integrate Bird setup into main flow"
```
---
### Task 11: Test End-to-End with Mock Mode
**Files:**
- None (testing only)
**Step 1: Test mock mode still works**
Run: `cd /Users/mvanhorn/last30days-skill-private && python3 scripts/last30days.py "Claude Code" --mock --emit=compact 2>&1 | head -20`
Expected: Output showing research results without errors
**Step 2: Test Bird detection (informational)**
Run: `cd /Users/mvanhorn/last30days-skill-private && python3 -c "from scripts.lib import env; import json; print(json.dumps(env.get_x_source_status(env.get_config()), indent=2))"`
Expected: JSON showing current Bird/xAI status
**Step 3: Commit any fixes if needed, then final commit**
```bash
git add -A
git commit -m "feat(bird): complete Bird CLI integration
- Add bird_x.py module for Bird CLI detection, install, and search
- Add UI prompts for interactive Bird installation
- Update env.py with X source priority (Bird > xAI)
- Integrate Bird into main research flow
- Bird uses browser cookies (free, no API key needed)"
```
---
### Task 12: Push to Private Repo
**Files:**
- None (git only)
**Step 1: Push all changes**
Run: `cd /Users/mvanhorn/last30days-skill-private && git push origin main`
Expected: Changes pushed to private repo
**Step 2: Verify commit history**
Run: `cd /Users/mvanhorn/last30days-skill-private && git log --oneline -10`
Expected: Shows Bird integration commits
---
## Summary
After completing all tasks, the skill will:
1. Check if Bird CLI is installed on startup
2. If not installed but npm available, prompt user to install
3. If installed, verify authentication via `bird whoami`
4. If authenticated, use Bird for all X searches (free)
5. If not, fall back to xAI (if key exists) or WebSearch
6. Output format identical regardless of backend used
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.