Match original NUX: non-blocking Bird promotion
- Remove interactive "Install Bird CLI?" prompt - Add Bird as FREE option in promo messages - Auto-detect Bird silently (use if authenticated) - Keep original flow: show promo → continue with available sources Bird now appears in the promo alongside API keys: 🔵 X (Twitter) └─ FREE: npm install -g @steipete/bird (uses browser session) └─ Or: Add XAI_API_KEY (paid API) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+9
-62
@@ -26,8 +26,6 @@ from pathlib import Path
|
|||||||
SCRIPT_DIR = Path(__file__).parent.resolve()
|
SCRIPT_DIR = Path(__file__).parent.resolve()
|
||||||
sys.path.insert(0, str(SCRIPT_DIR))
|
sys.path.insert(0, str(SCRIPT_DIR))
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from lib import (
|
from lib import (
|
||||||
bird_x,
|
bird_x,
|
||||||
dates,
|
dates,
|
||||||
@@ -47,47 +45,6 @@ from lib import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def load_fixture(name: str) -> dict:
|
def load_fixture(name: str) -> dict:
|
||||||
"""Load a fixture file."""
|
"""Load a fixture file."""
|
||||||
fixture_path = SCRIPT_DIR.parent / "fixtures" / name
|
fixture_path = SCRIPT_DIR.parent / "fixtures" / name
|
||||||
@@ -405,33 +362,23 @@ def main():
|
|||||||
else:
|
else:
|
||||||
depth = "default"
|
depth = "default"
|
||||||
|
|
||||||
# Load config first to check Bird availability
|
# Validate topic first (matches original NUX)
|
||||||
config = env.get_config()
|
|
||||||
|
|
||||||
# Check Bird availability and offer install if needed (before topic validation)
|
|
||||||
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"]:
|
|
||||||
# Create minimal progress for Bird prompts (no topic yet)
|
|
||||||
temp_progress = ui.ProgressDisplay("setup", show_banner=False)
|
|
||||||
bird_result = setup_bird_if_needed(temp_progress)
|
|
||||||
if bird_result == 'bird':
|
|
||||||
x_source = 'bird'
|
|
||||||
# Refresh status
|
|
||||||
x_source_status = env.get_x_source_status(config)
|
|
||||||
|
|
||||||
# Now validate topic
|
|
||||||
if not args.topic:
|
if not args.topic:
|
||||||
print("Error: Please provide a topic to research.", file=sys.stderr)
|
print("Error: Please provide a topic to research.", file=sys.stderr)
|
||||||
print("Usage: python3 last30days.py <topic> [options]", file=sys.stderr)
|
print("Usage: python3 last30days.py <topic> [options]", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Load config
|
||||||
|
config = env.get_config()
|
||||||
|
|
||||||
|
# Auto-detect Bird (no prompts - just use it if available)
|
||||||
|
x_source_status = env.get_x_source_status(config)
|
||||||
|
x_source = x_source_status["source"] # 'bird', 'xai', or None
|
||||||
|
|
||||||
# Initialize progress display with topic
|
# Initialize progress display with topic
|
||||||
progress = ui.ProgressDisplay(args.topic, show_banner=True)
|
progress = ui.ProgressDisplay(args.topic, show_banner=True)
|
||||||
|
|
||||||
# Check available sources (now accounting for Bird)
|
# Check available sources (accounting for Bird auto-detection)
|
||||||
available = env.get_available_sources(config)
|
available = env.get_available_sources(config)
|
||||||
|
|
||||||
# Override available if Bird is ready
|
# Override available if Bird is ready
|
||||||
|
|||||||
+11
-5
@@ -85,21 +85,27 @@ def get_available_sources(config: Dict[str, Any]) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def get_missing_keys(config: Dict[str, Any]) -> str:
|
def get_missing_keys(config: Dict[str, Any]) -> str:
|
||||||
"""Determine which API keys are missing.
|
"""Determine which sources are missing (accounting for Bird).
|
||||||
|
|
||||||
Returns: 'both', 'reddit', 'x', or 'none'
|
Returns: 'both', 'reddit', 'x', or 'none'
|
||||||
"""
|
"""
|
||||||
has_openai = bool(config.get('OPENAI_API_KEY'))
|
has_openai = bool(config.get('OPENAI_API_KEY'))
|
||||||
has_xai = bool(config.get('XAI_API_KEY'))
|
has_xai = bool(config.get('XAI_API_KEY'))
|
||||||
|
|
||||||
if has_openai and has_xai:
|
# Check if Bird provides X access (import here to avoid circular dependency)
|
||||||
|
from . import bird_x
|
||||||
|
has_bird = bird_x.is_bird_installed() and bird_x.is_bird_authenticated()
|
||||||
|
|
||||||
|
has_x = has_xai or has_bird
|
||||||
|
|
||||||
|
if has_openai and has_x:
|
||||||
return 'none'
|
return 'none'
|
||||||
elif has_openai:
|
elif has_openai:
|
||||||
return 'x' # Missing xAI key
|
return 'x' # Missing X source
|
||||||
elif has_xai:
|
elif has_x:
|
||||||
return 'reddit' # Missing OpenAI key
|
return 'reddit' # Missing OpenAI key
|
||||||
else:
|
else:
|
||||||
return 'both' # Missing both keys
|
return 'both' # Missing both
|
||||||
|
|
||||||
|
|
||||||
def validate_sources(requested: str, available: str, include_web: bool = False) -> tuple[str, Optional[str]]:
|
def validate_sources(requested: str, available: str, include_web: bool = False) -> tuple[str, Optional[str]]:
|
||||||
|
|||||||
+10
-6
@@ -84,13 +84,14 @@ PROMO_MESSAGE = f"""
|
|||||||
{Colors.YELLOW}{Colors.BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.RESET}
|
{Colors.YELLOW}{Colors.BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.RESET}
|
||||||
{Colors.YELLOW}⚡ UNLOCK THE FULL POWER OF /last30days{Colors.RESET}
|
{Colors.YELLOW}⚡ UNLOCK THE FULL POWER OF /last30days{Colors.RESET}
|
||||||
|
|
||||||
{Colors.DIM}Right now you're using web search only. Add API keys to unlock:{Colors.RESET}
|
{Colors.DIM}Right now you're using web search only. Unlock more sources:{Colors.RESET}
|
||||||
|
|
||||||
{Colors.YELLOW}🟠 Reddit{Colors.RESET} - Real upvotes, comments, and community insights
|
{Colors.YELLOW}🟠 Reddit{Colors.RESET} - Real upvotes, comments, and community insights
|
||||||
└─ Add OPENAI_API_KEY (uses OpenAI's web_search for Reddit)
|
└─ Add OPENAI_API_KEY (uses OpenAI's web_search for Reddit)
|
||||||
|
|
||||||
{Colors.CYAN}🔵 X (Twitter){Colors.RESET} - Real-time posts, likes, reposts from creators
|
{Colors.CYAN}🔵 X (Twitter){Colors.RESET} - Real-time posts, likes, reposts from creators
|
||||||
└─ Add XAI_API_KEY (uses xAI's live X search)
|
└─ {Colors.GREEN}FREE:{Colors.RESET} npm install -g @steipete/bird {Colors.DIM}(uses browser session){Colors.RESET}
|
||||||
|
└─ {Colors.DIM}Or:{Colors.RESET} Add XAI_API_KEY (paid API)
|
||||||
|
|
||||||
{Colors.DIM}Setup:{Colors.RESET} Edit {Colors.BOLD}~/.config/last30days/.env{Colors.RESET}
|
{Colors.DIM}Setup:{Colors.RESET} Edit {Colors.BOLD}~/.config/last30days/.env{Colors.RESET}
|
||||||
{Colors.YELLOW}{Colors.BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.RESET}
|
{Colors.YELLOW}{Colors.BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.RESET}
|
||||||
@@ -100,13 +101,14 @@ PROMO_MESSAGE_PLAIN = """
|
|||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
⚡ UNLOCK THE FULL POWER OF /last30days
|
⚡ UNLOCK THE FULL POWER OF /last30days
|
||||||
|
|
||||||
Right now you're using web search only. Add API keys to unlock:
|
Right now you're using web search only. Unlock more sources:
|
||||||
|
|
||||||
🟠 Reddit - Real upvotes, comments, and community insights
|
🟠 Reddit - Real upvotes, comments, and community insights
|
||||||
└─ Add OPENAI_API_KEY (uses OpenAI's web_search for Reddit)
|
└─ Add OPENAI_API_KEY (uses OpenAI's web_search for Reddit)
|
||||||
|
|
||||||
🔵 X (Twitter) - Real-time posts, likes, reposts from creators
|
🔵 X (Twitter) - Real-time posts, likes, reposts from creators
|
||||||
└─ Add XAI_API_KEY (uses xAI's live X search)
|
└─ FREE: npm install -g @steipete/bird (uses browser session)
|
||||||
|
└─ Or: Add XAI_API_KEY (paid API)
|
||||||
|
|
||||||
Setup: Edit ~/.config/last30days/.env
|
Setup: Edit ~/.config/last30days/.env
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
@@ -118,13 +120,15 @@ PROMO_SINGLE_KEY = {
|
|||||||
{Colors.DIM}💡 Tip: Add {Colors.YELLOW}OPENAI_API_KEY{Colors.RESET}{Colors.DIM} to ~/.config/last30days/.env for Reddit data with real engagement metrics!{Colors.RESET}
|
{Colors.DIM}💡 Tip: Add {Colors.YELLOW}OPENAI_API_KEY{Colors.RESET}{Colors.DIM} to ~/.config/last30days/.env for Reddit data with real engagement metrics!{Colors.RESET}
|
||||||
""",
|
""",
|
||||||
"x": f"""
|
"x": f"""
|
||||||
{Colors.DIM}💡 Tip: Add {Colors.CYAN}XAI_API_KEY{Colors.RESET}{Colors.DIM} to ~/.config/last30days/.env for X/Twitter data with real likes & reposts!{Colors.RESET}
|
{Colors.DIM}💡 Tip: For X/Twitter data with real likes & reposts:{Colors.RESET}
|
||||||
|
{Colors.GREEN}FREE:{Colors.RESET} npm install -g @steipete/bird {Colors.DIM}(uses browser session){Colors.RESET}
|
||||||
|
{Colors.DIM}Or: Add XAI_API_KEY to ~/.config/last30days/.env{Colors.RESET}
|
||||||
""",
|
""",
|
||||||
}
|
}
|
||||||
|
|
||||||
PROMO_SINGLE_KEY_PLAIN = {
|
PROMO_SINGLE_KEY_PLAIN = {
|
||||||
"reddit": "\n💡 Tip: Add OPENAI_API_KEY to ~/.config/last30days/.env for Reddit data with real engagement metrics!\n",
|
"reddit": "\n💡 Tip: Add OPENAI_API_KEY to ~/.config/last30days/.env for Reddit data with real engagement metrics!\n",
|
||||||
"x": "\n💡 Tip: Add XAI_API_KEY to ~/.config/last30days/.env for X/Twitter data with real likes & reposts!\n",
|
"x": "\n💡 Tip: For X/Twitter data with real likes & reposts:\n FREE: npm install -g @steipete/bird (uses browser session)\n Or: Add XAI_API_KEY to ~/.config/last30days/.env\n",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Bird CLI prompts
|
# Bird CLI prompts
|
||||||
|
|||||||
Reference in New Issue
Block a user