Initial commit: Screen Leads app

Screen-capture lead tool: capture agent, Claude vision extractor with
pluggable site recipes (LinkedIn), canonical funnel model, SQLite storage,
FastAPI backend, dashboard, and setup/run scripts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 07:43:33 +05:30
commit c1eef4c6aa
32 changed files with 1688 additions and 0 deletions
View File
+70
View File
@@ -0,0 +1,70 @@
"""Front-tab detection: decide whether the current page is an enabled site's
capturable target (a LinkedIn profile, to start).
Strategy:
1. Try to read the browser URL natively (fast, exact; macOS reliably, others
best-effort).
2. If no URL, capture one screenshot and let the AI read the address bar /
recognise the site visually (portable, screen-only).
3. Ask the recipe registry whether any enabled recipe matches.
"""
from __future__ import annotations
from typing import Optional
from agent import screenshot
from agent.platform import get_active_tab_url
from ai import recipes
from core.models import DetectionResult, PageContext
def detect(use_ai_fallback: bool = True) -> DetectionResult:
url = get_active_tab_url()
ctx = PageContext(url=url)
method = "url" if url else ""
hit = recipes.match(ctx)
if hit is None and url is None and use_ai_fallback:
# No native URL and nothing matched — read the screen visually.
try:
shot = screenshot.capture(prefix="detect")
from ai import extractor
info = extractor.classify_page(shot)
ctx.ai_site = info.get("site")
ctx.ai_page_type = info.get("page_type")
ctx.ai_url_text = info.get("address_bar_url")
method = "visual"
hit = recipes.match(ctx)
except Exception as e:
return DetectionResult(ok=False, reason=f"detection failed: {e}")
if hit is None:
where = ctx.best_url or "the current tab"
return DetectionResult(
ok=False,
reason=f"Front tab is not a supported site ({where}).",
url=ctx.best_url,
method=method,
)
recipe, pm = hit
if not pm.is_target:
return DetectionResult(
ok=False,
reason=(f"Front tab is {recipe.display_name} but not a "
f"{recipe.target_page_type} page (got '{pm.page_type}')."),
recipe_id=recipe.site_id,
page_type=pm.page_type,
url=ctx.best_url,
method=method,
)
return DetectionResult(
ok=True,
recipe_id=recipe.site_id,
page_type=pm.page_type,
url=ctx.best_url,
method=method,
)
+30
View File
@@ -0,0 +1,30 @@
"""Cross-platform helper for reading the front browser tab's URL.
This is the one genuinely OS-specific piece. Reading *your own* browser's
current URL is a passive, local operation (no request to the target site), so
it stays within the screen-capture / compliance posture. When it can't return
a URL (common on Windows/Linux), the detector falls back to reading the address
bar visually from a screenshot via the AI.
"""
import sys
from typing import Optional
def get_active_tab_url() -> Optional[str]:
"""Best-effort URL of the frontmost browser tab, or None if unavailable."""
try:
if sys.platform == "darwin":
from . import macos
return macos.get_active_tab_url()
if sys.platform.startswith("linux"):
from . import linux
return linux.get_active_tab_url()
if sys.platform in ("win32", "cygwin"):
from . import windows
return windows.get_active_tab_url()
except Exception:
return None
return None
+11
View File
@@ -0,0 +1,11 @@
"""Linux (X11): the active window *title* is reachable via xdotool, but the URL
is not exposed without a browser extension or the accessibility bus. We return
None so the detector falls back to reading the address bar visually from the
screenshot, which is the portable path. (Wayland exposes neither; the app warns
about that separately.)
"""
from typing import Optional
def get_active_tab_url() -> Optional[str]:
return None
+39
View File
@@ -0,0 +1,39 @@
"""macOS: read the frontmost browser tab URL via AppleScript.
Reads local browser state only (no network call to the target site). Requires
the terminal/app running this to have Automation permission for the browser.
"""
import subprocess
from typing import Optional
# Browsers that expose `URL of active tab of front window` (Chromium family)
# or `URL of front document` (Safari).
_CHROMIUM = ["Google Chrome", "Brave Browser", "Microsoft Edge", "Arc", "Vivaldi", "Chromium"]
def _frontmost_app() -> Optional[str]:
script = 'tell application "System Events" to get name of first process whose frontmost is true'
return _osascript(script)
def _osascript(script: str) -> Optional[str]:
try:
out = subprocess.run(
["osascript", "-e", script],
capture_output=True, text=True, timeout=5,
)
val = out.stdout.strip()
return val or None
except Exception:
return None
def get_active_tab_url() -> Optional[str]:
app = _frontmost_app()
if not app:
return None
if app in _CHROMIUM:
return _osascript(f'tell application "{app}" to get URL of active tab of front window')
if app == "Safari":
return _osascript('tell application "Safari" to get URL of front document')
return None
+24
View File
@@ -0,0 +1,24 @@
"""Windows: reliably reading a browser's address-bar URL needs UI Automation
(pywinauto/uiautomation). We attempt a lightweight UIA read if available and
otherwise return None, letting the detector fall back to visual address-bar
reading from the screenshot.
"""
from typing import Optional
def get_active_tab_url() -> Optional[str]:
try:
import uiautomation as auto # optional dependency
except Exception:
return None
try:
window = auto.GetForegroundControl()
# Chromium/Firefox expose the address bar as an Edit control named
# "Address and search bar" / "Search or enter address".
edit = window.EditControl(searchDepth=12)
if edit.Exists(0.3):
val = (edit.GetValuePattern().Value or "").strip()
return val or None
except Exception:
return None
return None
+35
View File
@@ -0,0 +1,35 @@
"""Cross-platform full-screen capture via `mss` (Windows, macOS, Linux/X11)."""
from __future__ import annotations
import time
from pathlib import Path
from typing import Optional
from config import SCREENSHOT_DIR
class CaptureError(RuntimeError):
pass
def capture(prefix: str = "shot") -> str:
"""Grab the primary monitor to a PNG and return its path."""
try:
import mss
import mss.tools
except Exception as e: # pragma: no cover
raise CaptureError(f"mss not available: {e}")
ts = int(time.time() * 1000)
path = SCREENSHOT_DIR / f"{prefix}_{ts}.png"
try:
with mss.mss() as sct:
monitor = sct.monitors[1] # [0] is the virtual "all monitors" rect
img = sct.grab(monitor)
mss.tools.to_png(img.rgb, img.size, output=str(path))
except Exception as e:
raise CaptureError(
f"Screen capture failed ({e}). On macOS grant Screen Recording; "
f"on Linux use an X11 session (Wayland needs the screenshot portal)."
)
return str(path)
+42
View File
@@ -0,0 +1,42 @@
"""OS-level scrolling of the focused window via pyautogui.
We drive real scroll input rather than the DOM, so it works over a plain
screenshot pipeline on any OS. Needs Accessibility permission on macOS and an
X11 session on Linux.
"""
from __future__ import annotations
import time
from config import SCROLL_AMOUNT, SCROLL_PAUSE
class ScrollError(RuntimeError):
pass
def _pyautogui():
try:
import pyautogui
pyautogui.FAILSAFE = False
return pyautogui
except Exception as e: # pragma: no cover
raise ScrollError(
f"pyautogui not available ({e}). On macOS grant Accessibility; "
f"on Linux use X11."
)
def scroll_to_top() -> None:
pg = _pyautogui()
# A large upward scroll reliably returns most pages to the top.
for _ in range(10):
pg.scroll(SCROLL_AMOUNT)
time.sleep(SCROLL_PAUSE)
def scroll_down(amount: int = SCROLL_AMOUNT) -> None:
pg = _pyautogui()
pg.scroll(-abs(amount))
time.sleep(SCROLL_PAUSE)