Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.3] - 2025-12-27

### Changed
- **Version display**: Fix hardcoded version display
- **Logs display**: Remove agent logs, claude agent sdk logs
- **Browser-use installation**: Better instructions on how to install bu for agent mode

## [0.2.2] - 2025-12-26

### Changed
Expand Down
18 changes: 8 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,20 @@ CLI tool that captures browser traffic and automatically generates production-re
# Basic installation (manual and engineer modes)
pip install reverse-api-engineer

# With agent mode support (includes browser-use)
# With agent mode support
pip install 'reverse-api-engineer[agent]'
pip install git+https://github.com/browser-use/browser-use.git@49a345fb19e9f12befc5cc1658e0033873892455T

# Install browser-use with HAR recording support (required for agent mode)
pip install git+https://github.com/browser-use/browser-use.git@49a345fb19e9f12befc5cc1658e0033873892455
```

### Using uv (recommended)
```bash
# Basic installation
uv tool install reverse-api-engineer

# With agent mode support
uv tool install 'reverse-api-engineer[agent]'
uv pip install git+https://github.com/browser-use/browser-use.git@49a345fb19e9f12befc5cc1658e0033873892455
# With agent mode support (includes browser-use with HAR recording)
uv tool install 'reverse-api-engineer[agent]' --with 'browser-use @ git+https://github.com/browser-use/browser-use.git@49a345fb19e9f12befc5cc1658e0033873892455'
```

### From source
Expand Down Expand Up @@ -106,7 +107,7 @@ reverse-api-engineer engineer <run_id>

Fully automated browser interaction using AI agents:

1. Install with agent support: `pip install 'reverse-api-engineer[agent]'` or `uv tool install 'reverse-api-engineer[agent]'`
1. Install with agent support (see installation instructions above for pip or uv)
2. Start the CLI: `reverse-api-engineer`
3. Switch to agent mode (Shift+Tab until you see `[agent]`)
4. Enter your task description (e.g., "Click on the first job listing")
Expand All @@ -115,10 +116,7 @@ Fully automated browser interaction using AI agents:
7. HAR is captured automatically
8. Optionally run reverse engineering to generate API client

**Note:** If you need a specific version of browser-use from git, install it separately:
```bash
pip install git+https://github.com/browser-use/browser-use.git@<commit-hash>
```
**Important:** Browser-use must be installed from the specific git commit shown in the installation instructions above. This version includes HAR recording support which is not yet available in the PyPI release.

**Agent Provider Configuration:**
- **Browser-Use** (default): Supports Browser-Use LLM, OpenAI, and Google models
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "reverse-api-engineer"
version = "0.2.2"
version = "0.2.3"
description = "A tool to capture browser traffic for API reverse engineering"
readme = "README.md"
requires-python = ">=3.11"
Expand Down Expand Up @@ -35,7 +35,8 @@ dependencies = [

[project.optional-dependencies]
agent = [
"browser-use",
# browser-use must be installed separately from git (PyPI doesn't allow git dependencies)
# See installation instructions in README
"stagehand"
]

Expand Down
2 changes: 1 addition & 1 deletion src/reverse_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Reverse API - Browser traffic capture for API reverse engineering."""

__version__ = "0.2.2"
__version__ = "0.2.3"
133 changes: 46 additions & 87 deletions src/reverse_api/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from rich.status import Status

from .utils import get_har_dir, get_timestamp
from . import __version__

console = Console()

Expand Down Expand Up @@ -386,7 +387,7 @@ def _start_with_stealth_chromium(self, start_url: Optional[str] = None) -> Path:
# Wait for browser to close
try:
while self._context.pages:
self._context.pages[0].wait_for_timeout(100)
self._context.pages[0].wait_for_timeout(100)
except Exception:
pass

Expand Down Expand Up @@ -435,7 +436,7 @@ def close(self) -> Path:
time.sleep(1)

status.update(" [dim]saving har file...[/dim]")
self._context.close()
self._context.close()

if self.har_path.exists():
har_size = self.har_path.stat().st_size
Expand Down Expand Up @@ -675,69 +676,43 @@ async def _run_with_har_capture(self, cdp_url: str = None) -> dict:
async def _run_with_browser_use(self) -> dict:
"""Run agent with HAR recording via browser-use's built-in HAR capture."""
import logging
import os

# Custom handler to capture memory logs
class MemoryLogHandler(logging.Handler):
def __init__(self, console_instance):
super().__init__()
self.console = console_instance

def emit(self, record):
try:
msg = record.getMessage()

if "🧠 Memory:" in msg or ("Memory:" in msg and "🧠" not in msg):
if "🧠 Memory:" in msg:
memory_text = msg.split("🧠 Memory:", 1)[-1].strip()
elif "Memory:" in msg:
memory_text = msg.split("Memory:", 1)[-1].strip()
else:
memory_text = msg

memory_text = " ".join(memory_text.split())

if memory_text:
self.console.print(f" [dim]{memory_text}[/dim]")
except Exception:
pass # Silently ignore handler errors

# Suppress all browser-use logs completely
# Keep INFO level for memory capture, but prevent propagation to parent handlers
null_handler = logging.NullHandler()

browser_use_logger = logging.getLogger("browser_use")
browser_use_logger.setLevel(logging.INFO)
browser_use_logger.handlers.clear() # Remove existing handlers
browser_use_logger.propagate = False

agent_logger = logging.getLogger("Agent")
agent_logger.setLevel(logging.INFO)
agent_logger.handlers.clear() # Remove existing handlers
agent_logger.propagate = False

# Suppress these loggers completely
for logger_name in ["BrowserSession", "service", "tools"]:
logger = logging.getLogger(logger_name)
logger.setLevel(logging.CRITICAL)
logger.handlers.clear()
logger.addHandler(null_handler)
logger.propagate = False
# Set before importing to ensure it takes effect
os.environ['BROWSER_USE_LOGGING_LEVEL'] = 'WARNING'
Comment thread
kalil0321 marked this conversation as resolved.

# Import browser-use after setting environment variable
try:
from browser_use import Agent, Browser
from browser_use import ChatBrowserUse

# Suppress all browser-use loggers after import
def suppress_browser_use_logs():
for logger_name in [
"browser_use",
"Agent",
"BrowserSession",
"service",
"tools",
]:
logger = logging.getLogger(logger_name)
logger.setLevel(logging.CRITICAL)
logger.handlers.clear()
logger.addHandler(logging.NullHandler())
logger.propagate = False

suppress_browser_use_logs()
except ImportError:
result = {
"success": False,
"message": None,
"error": "browser-use is required for agent mode. Install it with: pip install 'reverse-api-engineer[agent]' or pip install browser-use",
"error": "browser-use is required for agent mode. Install it with: pip install git+https://github.com/browser-use/browser-use.git@49a345fb19e9f12befc5cc1658e0033873892455",
}
console.print(f" [red]error:[/red] {result['error']}")
return result

result = {"success": False, "message": None, "error": None}
browser = None
memory_handler = None

try:
# Parse agent model and validate API key
Expand Down Expand Up @@ -786,60 +761,41 @@ def emit(self, record):
console.print(f" [red]error:[/red] {result['error']}")
return result

suppress_browser_use_logs()

console.print(f" [dim]starting browser with har...[/dim]")
browser = Browser(
record_har_path=str(self.har_path),
record_har_mode="full",
record_har_content="attach",
)

suppress_browser_use_logs()

await browser.start()

suppress_browser_use_logs()

console.print(f" [dim]browser started[/dim]")

task = self.prompt

# Set up memory log handler
memory_handler = MemoryLogHandler(console)
memory_handler.setLevel(logging.INFO)
browser_use_logger.addHandler(memory_handler)
agent_logger.addHandler(memory_handler)

agent = Agent(task=task, llm=llm, browser=browser)
agent_result = await agent.run()

if memory_handler:
browser_use_logger.removeHandler(memory_handler)
agent_logger.removeHandler(memory_handler)
suppress_browser_use_logs()

agent_result = await agent.run()

# Extract final result from agent_result
# Extract final result using browser-use's built-in method
final_message = None
if agent_result:
if hasattr(agent_result, "all_results") and agent_result.all_results:
last_result = agent_result.all_results[-1]
if hasattr(last_result, "text") and last_result.text:
final_message = last_result.text
elif hasattr(last_result, "result") and last_result.result:
final_message = str(last_result.result)
elif hasattr(agent_result, "result"):
final_message = str(agent_result.result)
elif hasattr(agent_result, "text"):
final_message = agent_result.text
else:
msg_str = str(agent_result)
if "Final Result:" in msg_str:
parts = msg_str.split("Final Result:")
if len(parts) > 1:
final_message = parts[-1].strip()
else:
final_message = msg_str
if agent_result and hasattr(agent_result, "final_result"):
final_result_attr = agent_result.final_result
final_message = final_result_attr() if callable(final_result_attr) else final_result_attr

result["success"] = True
result["message"] = final_message or "Task completed"

except Exception as e:
if memory_handler:
browser_use_logger.removeHandler(memory_handler)
agent_logger.removeHandler(memory_handler)
result["error"] = str(e)
console.print(f" [yellow]agent error: {e}[/yellow]")
finally:
Expand Down Expand Up @@ -908,7 +864,10 @@ async def _run_with_stagehand(self) -> dict:
empty_har = {
"log": {
"version": "1.2",
"creator": {"name": "reverse-api-engineer", "version": "0.2.0"},
"creator": {
"name": "reverse-api-engineer",
"version": __version__,
},
"pages": [],
"entries": [],
}
Expand Down Expand Up @@ -1062,11 +1021,11 @@ def start(self) -> Path:

if result.get("success"):
console.print(f" [green]agent task completed[/green]")
if result.get("message"):
msg = result["message"]
msg = result.get("message", "").strip()
if msg:
if len(msg) > 500:
msg = msg[:500] + "..."
console.print(f" [dim]result:[/dim] [white]{msg}[/white]")
console.print(f" [dim]result:[/dim]\n [white]{msg}[/white]")
else:
error = result.get("error", "unknown error")
console.print(f" [yellow]agent error: {error}[/yellow]")
Expand Down
5 changes: 5 additions & 0 deletions src/reverse_api/engineer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Reverse engineering module with SDK dispatch."""

import asyncio
import logging
from pathlib import Path
from typing import Optional, Dict, Any

Expand All @@ -16,6 +17,10 @@

from .base_engineer import BaseEngineer

# Suppress claude_agent_sdk logs
logging.getLogger("claude_agent_sdk").setLevel(logging.WARNING)
logging.getLogger("claude_agent_sdk._internal.transport.subprocess_cli").setLevel(logging.WARNING)


class ClaudeEngineer(BaseEngineer):
"""Uses Claude Agent SDK to analyze HAR files and generate Python API scripts."""
Expand Down
Loading