fix: suppress loguru noise in CLI, fix birdx text parser
- CLI now suppresses loguru INFO logs by default (use -v/--verbose to show) - Fixed birdx output parser to correctly extract author, url, date, text - birdx search now uses plain text output (--json returns empty arrays)
This commit is contained in:
@@ -17,15 +17,25 @@ import sys
|
|||||||
import asyncio
|
import asyncio
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
from agent_eyes import __version__
|
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():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
prog="agent-eyes",
|
prog="agent-eyes",
|
||||||
description="👁️ Give your AI Agent eyes to see the entire internet",
|
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")
|
sub = parser.add_subparsers(dest="command", help="Available commands")
|
||||||
|
|
||||||
# ── read ──
|
# ── read ──
|
||||||
@@ -66,6 +76,9 @@ def main():
|
|||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Suppress loguru noise unless --verbose
|
||||||
|
_configure_logging(getattr(args, "verbose", False))
|
||||||
|
|
||||||
if not args.command:
|
if not args.command:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|||||||
@@ -38,46 +38,74 @@ async def _search_birdx(query: str, limit: int) -> List[Dict[str, Any]]:
|
|||||||
"""Search Twitter via birdx CLI."""
|
"""Search Twitter via birdx CLI."""
|
||||||
logger.info(f"birdx search: {query} (n={limit})")
|
logger.info(f"birdx search: {query} (n={limit})")
|
||||||
try:
|
try:
|
||||||
|
# birdx --json returns [] for search, so use plain text output
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["birdx", "search", query, "-n", str(limit), "--json"],
|
["birdx", "search", query, "-n", str(limit)],
|
||||||
capture_output=True, text=True, timeout=30,
|
capture_output=True, text=True, timeout=30,
|
||||||
)
|
)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
# birdx might not support --json, try plain output
|
logger.error(f"birdx search failed: {result.stderr}")
|
||||||
result = subprocess.run(
|
return []
|
||||||
["birdx", "search", query, "-n", str(limit)],
|
return _parse_birdx_text(result.stdout)
|
||||||
capture_output=True, text=True, timeout=30,
|
except (subprocess.TimeoutExpired, FileNotFoundError) as e:
|
||||||
)
|
|
||||||
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: {e}")
|
logger.error(f"birdx search failed: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def _parse_birdx_text(text: str) -> List[Dict[str, Any]]:
|
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 = []
|
results = []
|
||||||
current = {}
|
current: Dict[str, Any] = {}
|
||||||
|
text_lines = []
|
||||||
|
|
||||||
for line in text.strip().split("\n"):
|
for line in text.strip().split("\n"):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line:
|
|
||||||
|
# Separator between tweets
|
||||||
|
if line.startswith("─"):
|
||||||
if current:
|
if current:
|
||||||
|
if text_lines:
|
||||||
|
current["text"] = "\n".join(text_lines).strip()
|
||||||
results.append(current)
|
results.append(current)
|
||||||
current = {}
|
current = {}
|
||||||
|
text_lines = []
|
||||||
continue
|
continue
|
||||||
if line.startswith("@"):
|
|
||||||
current["author"] = line.split()[0] if line else ""
|
# Author line: @handle (Display Name):
|
||||||
elif line.startswith("http"):
|
if line.startswith("@") and line.endswith(":") and "(" in line:
|
||||||
current["url"] = line
|
handle = line.split()[0]
|
||||||
else:
|
current["author"] = handle
|
||||||
current["text"] = current.get("text", "") + " " + line
|
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 current:
|
||||||
|
if text_lines:
|
||||||
|
current["text"] = "\n".join(text_lines).strip()
|
||||||
results.append(current)
|
results.append(current)
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user