diff --git a/agent_eyes/cli.py b/agent_eyes/cli.py index 619f889..11685d5 100644 --- a/agent_eyes/cli.py +++ b/agent_eyes/cli.py @@ -17,15 +17,25 @@ import sys import asyncio import argparse import json +import os from agent_eyes import __version__ +def _configure_logging(verbose: bool = False): + """Suppress loguru output unless --verbose is set.""" + from loguru import logger + logger.remove() # Remove default stderr handler + if verbose: + logger.add(sys.stderr, level="INFO") + + def main(): parser = argparse.ArgumentParser( prog="agent-eyes", description="👁️ Give your AI Agent eyes to see the entire internet", ) + parser.add_argument("-v", "--verbose", action="store_true", help="Show debug logs") sub = parser.add_subparsers(dest="command", help="Available commands") # ── read ── @@ -66,6 +76,9 @@ def main(): args = parser.parse_args() + # Suppress loguru noise unless --verbose + _configure_logging(getattr(args, "verbose", False)) + if not args.command: parser.print_help() sys.exit(0) diff --git a/agent_eyes/search/twitter.py b/agent_eyes/search/twitter.py index 819acd7..1c28c8a 100644 --- a/agent_eyes/search/twitter.py +++ b/agent_eyes/search/twitter.py @@ -38,46 +38,74 @@ async def _search_birdx(query: str, limit: int) -> List[Dict[str, Any]]: """Search Twitter via birdx CLI.""" logger.info(f"birdx search: {query} (n={limit})") try: + # birdx --json returns [] for search, so use plain text output result = subprocess.run( - ["birdx", "search", query, "-n", str(limit), "--json"], + ["birdx", "search", query, "-n", str(limit)], capture_output=True, text=True, timeout=30, ) if result.returncode != 0: - # birdx might not support --json, try plain output - result = subprocess.run( - ["birdx", "search", query, "-n", str(limit)], - capture_output=True, text=True, timeout=30, - ) - return _parse_birdx_text(result.stdout) - - data = json.loads(result.stdout) - if isinstance(data, list): - return data - return data.get("tweets", data.get("results", [])) - except (subprocess.TimeoutExpired, json.JSONDecodeError, FileNotFoundError) as e: + logger.error(f"birdx search failed: {result.stderr}") + return [] + return _parse_birdx_text(result.stdout) + except (subprocess.TimeoutExpired, FileNotFoundError) as e: logger.error(f"birdx search failed: {e}") return [] def _parse_birdx_text(text: str) -> List[Dict[str, Any]]: - """Parse birdx plain text output into structured data.""" + """Parse birdx plain text output into structured data. + + Format: + @handle (Display Name): + Tweet text here + possibly multiple lines + date: Mon Feb 24 12:00:00 +0000 2026 + url: https://x.com/handle/status/123 + ────────────────────────────────────────────────── + """ results = [] - current = {} + current: Dict[str, Any] = {} + text_lines = [] + for line in text.strip().split("\n"): line = line.strip() - if not line: + + # Separator between tweets + if line.startswith("─"): if current: + if text_lines: + current["text"] = "\n".join(text_lines).strip() results.append(current) current = {} + text_lines = [] continue - if line.startswith("@"): - current["author"] = line.split()[0] if line else "" - elif line.startswith("http"): - current["url"] = line - else: - current["text"] = current.get("text", "") + " " + line + + # Author line: @handle (Display Name): + if line.startswith("@") and line.endswith(":") and "(" in line: + handle = line.split()[0] + current["author"] = handle + continue + + # Date line + if line.startswith("date:"): + current["date"] = line[5:].strip() + continue + + # URL line + if line.startswith("url:"): + current["url"] = line[4:].strip() + continue + + # Content line + if current: + text_lines.append(line) + + # Last tweet if current: + if text_lines: + current["text"] = "\n".join(text_lines).strip() results.append(current) + return results