robin / main.py
pjpjq's picture
Allow custom model IDs and ignore env proxies for Tor
d396650
import sys
import click
from yaspin import yaspin
from datetime import datetime
from scrape import scrape_multiple
from search import get_search_results
from llm import get_llm, refine_query, filter_results, generate_summary
@click.group()
@click.version_option()
def robin():
"""Robin: AI-Powered Dark Web OSINT Tool."""
pass
@robin.command()
@click.option(
"--model", "-m",
default="gpt-5-mini",
show_default=True,
type=str,
help="LLM model name (supports custom model IDs for OpenAI-compatible endpoints).",
)
@click.option("--query", "-q", required=True, type=str, help="Dark web search query")
@click.option("--threads", "-t", default=5, show_default=True, type=int, help="Number of threads (Default: 5)")
@click.option("--output", "-o", type=str, help="Filename to save the final summary.")
def cli(model, query, threads, output):
"""Run Robin in CLI mode."""
try:
llm = get_llm(model)
# Show spinner while processing
with yaspin(text="Processing...", color="cyan") as sp:
sp.write(f"๐Ÿ”น Initializing with model: {model}")
refined_query = refine_query(llm, query)
sp.write(f"๐Ÿ”น Refined Query: {refined_query}")
search_results = get_search_results(refined_query, max_workers=threads)
if not search_results:
sp.fail("โœ–")
click.echo("\n[ERROR] No search results found. Tor may be unstable or query returned 0 hits.")
return
sp.write(f"๐Ÿ”น Found {len(search_results)} raw results. Filtering...")
search_filtered = filter_results(llm, refined_query, search_results)
sp.write(f"๐Ÿ”น Scraping {len(search_filtered)} relevant sites...")
scraped_results = scrape_multiple(search_filtered, max_workers=threads)
if not scraped_results:
sp.fail("โœ–")
click.echo("\n[ERROR] Failed to scrape any content. Check Tor connection.")
return
sp.ok("โœ”")
# Generate summary
click.echo("\n๐Ÿ”น Generating Intelligence Summary...")
summary = generate_summary(llm, query, scraped_results)
# Save output
if not output:
now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
filename = f"summary_{now}.md"
else:
filename = output + ".md"
with open(filename, "w", encoding="utf-8") as f:
f.write(summary)
click.echo(f"\n[OUTPUT] Final intelligence summary saved to {filename}")
except KeyboardInterrupt:
click.echo("\n\n[!] Operation cancelled by user. Exiting.")
sys.exit(0)
except Exception as e:
click.echo(f"\n[ERROR] An unexpected error occurred: {e}")
sys.exit(1)
@robin.command()
@click.option("--ui-port", default=8501, show_default=True, type=int, help="Port for Streamlit UI")
@click.option("--ui-host", default="localhost", show_default=True, type=str, help="Host for Streamlit UI")
def ui(ui_port, ui_host):
"""Run Robin in Web UI mode."""
import sys, os
from streamlit.web import cli as stcli
if getattr(sys, "frozen", False):
base = sys._MEIPASS
else:
base = os.path.dirname(__file__)
ui_script = os.path.join(base, "ui.py")
sys.argv = [
"streamlit", "run", ui_script,
f"--server.port={ui_port}",
f"--server.address={ui_host}",
"--global.developmentMode=false",
]
sys.exit(stcli.main())
if __name__ == "__main__":
robin()