Skip to content

Commit a2244ad

Browse files
Merge pull request #5764 from aden-hive/feat/google-scopes
Feat/google scopes
2 parents b14b8f8 + 7608ba9 commit a2244ad

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+4311
-884
lines changed

core/frontend/src/pages/home.tsx

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState, useEffect, useRef } from "react";
22
import { useNavigate } from "react-router-dom";
3-
import { Crown, Mail, Briefcase, Shield, Search, Newspaper, ArrowRight, Hexagon, Send, Bot } from "lucide-react";
3+
import { Crown, Mail, Briefcase, Shield, Search, Newspaper, ArrowRight, Hexagon, Send, Bot, Radar, Reply, DollarSign, MapPin, Calendar, UserPlus, Twitter } from "lucide-react";
44
import TopBar from "@/components/TopBar";
55
import type { LucideIcon } from "lucide-react";
66
import { agentsApi } from "@/api/agents";
@@ -14,6 +14,13 @@ const AGENT_ICONS: Record<string, LucideIcon> = {
1414
vulnerability_assessment: Shield,
1515
deep_research_agent: Search,
1616
tech_news_reporter: Newspaper,
17+
competitive_intel_agent: Radar,
18+
email_reply_agent: Reply,
19+
hubspot_revenue_leak_detector: DollarSign,
20+
local_business_extractor: MapPin,
21+
meeting_scheduler: Calendar,
22+
sdr_agent: UserPlus,
23+
twitter_news_agent: Twitter,
1724
};
1825

1926
const AGENT_COLORS: Record<string, string> = {
@@ -22,6 +29,13 @@ const AGENT_COLORS: Record<string, string> = {
2229
vulnerability_assessment: "hsl(15,70%,52%)",
2330
deep_research_agent: "hsl(210,70%,55%)",
2431
tech_news_reporter: "hsl(270,60%,55%)",
32+
competitive_intel_agent: "hsl(190,70%,45%)",
33+
email_reply_agent: "hsl(45,80%,55%)",
34+
hubspot_revenue_leak_detector: "hsl(145,60%,42%)",
35+
local_business_extractor: "hsl(350,65%,55%)",
36+
meeting_scheduler: "hsl(220,65%,55%)",
37+
sdr_agent: "hsl(165,55%,45%)",
38+
twitter_news_agent: "hsl(200,85%,55%)",
2539
};
2640

2741
function agentSlug(path: string): string {
@@ -79,14 +93,12 @@ export default function Home() {
7993
};
8094

8195
return (
82-
<div className="min-h-screen h-screen bg-background flex flex-col overflow-hidden">
96+
<div className="min-h-screen bg-background flex flex-col">
8397
<TopBar />
8498

85-
{/* Body: main */}
86-
<div className="flex flex-1 min-h-0">
87-
{/* Main content */}
88-
<div className="flex-1 flex flex-col items-center justify-center p-6 overflow-y-auto">
89-
<div className="w-full max-w-2xl">
99+
{/* Main content */}
100+
<div className="flex-1 flex flex-col items-center justify-center p-6">
101+
<div className="w-full max-w-2xl">
90102
{/* Queen Bee greeting */}
91103
<div className="text-center mb-8">
92104
<div

core/frontend/src/pages/my-agents.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ export default function MyAgents() {
4141
const idleCount = agents.length - activeCount;
4242

4343
return (
44-
<div className="min-h-screen bg-background flex flex-col">
44+
<div className="h-screen bg-background flex flex-col overflow-hidden">
4545
<TopBar />
4646

4747
{/* Content */}
48-
<div className="flex-1 p-6 md:p-10 max-w-5xl mx-auto w-full">
48+
<div className="flex-1 p-6 md:p-10 max-w-5xl mx-auto w-full overflow-y-auto">
4949
<div className="flex items-center justify-between mb-8">
5050
<div>
5151
<h1 className="text-xl font-semibold text-foreground">My Agents</h1>

examples/templates/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@ uv run python -m exports.my_research_agent --input '{"topic": "..."}'
4343
| Template | Description |
4444
|----------|-------------|
4545
| [deep_research_agent](deep_research_agent/) | Interactive research agent that searches diverse sources, evaluates findings with user checkpoints, and produces a cited HTML report |
46+
| [local_business_extractor](local_business_extractor/) | Finds local businesses on Google Maps, scrapes contact details, and syncs to Google Sheets |
4647
| [tech_news_reporter](tech_news_reporter/) | Researches the latest technology and AI news from the web and produces a well-organized report |
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Local Business Extractor
2+
3+
Finds local businesses on Google Maps, scrapes their websites for contact details, and syncs everything to a Google Sheets spreadsheet.
4+
5+
## Nodes
6+
7+
| Node | Type | Description |
8+
|------|------|-------------|
9+
| `map-search-worker` | `gcu` (browser) | Searches Google Maps and extracts business names + website URLs |
10+
| `extract-contacts` | `event_loop` | Scrapes business websites for emails, phone, hours, reviews, address |
11+
| `sheets-sync` | `event_loop` | Appends extracted data to a Google Sheets spreadsheet |
12+
13+
## Flow
14+
15+
```
16+
extract-contacts → sheets-sync → (loop back to extract-contacts)
17+
18+
map-search-worker (sub-agent)
19+
```
20+
21+
## Tools used
22+
23+
- **Exa**`exa_search`, `exa_get_contents` for web scraping
24+
- **Google Sheets**`google_sheets_create_spreadsheet`, `google_sheets_update_values`, `google_sheets_append_values`, `google_sheets_get_values`
25+
- **Browser (GCU)** — automated Google Maps browsing
26+
27+
## Running
28+
29+
```bash
30+
uv run python -m examples.templates.local_business_extractor run --query "bakeries in San Francisco"
31+
```
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Local Business Extractor package."""
2+
3+
from .agent import (
4+
LocalBusinessExtractor,
5+
default_agent,
6+
goal,
7+
nodes,
8+
edges,
9+
entry_node,
10+
entry_points,
11+
pause_nodes,
12+
terminal_nodes,
13+
conversation_mode,
14+
identity_prompt,
15+
loop_config,
16+
)
17+
from .config import default_config, metadata
18+
19+
__all__ = [
20+
"LocalBusinessExtractor",
21+
"default_agent",
22+
"goal",
23+
"nodes",
24+
"edges",
25+
"entry_node",
26+
"entry_points",
27+
"pause_nodes",
28+
"terminal_nodes",
29+
"conversation_mode",
30+
"identity_prompt",
31+
"loop_config",
32+
"default_config",
33+
"metadata",
34+
]
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
"""
2+
CLI entry point for Local Business Extractor.
3+
"""
4+
5+
import asyncio
6+
import json
7+
import logging
8+
import sys
9+
import click
10+
11+
from .agent import default_agent, LocalBusinessExtractor
12+
13+
14+
def setup_logging(verbose=False, debug=False):
15+
"""Configure logging for execution visibility."""
16+
if debug:
17+
level, fmt = logging.DEBUG, "%(asctime)s %(name)s: %(message)s"
18+
elif verbose:
19+
level, fmt = logging.INFO, "%(message)s"
20+
else:
21+
level, fmt = logging.WARNING, "%(levelname)s: %(message)s"
22+
logging.basicConfig(level=level, format=fmt, stream=sys.stderr)
23+
logging.getLogger("framework").setLevel(level)
24+
25+
26+
@click.group()
27+
@click.version_option(version="1.0.0")
28+
def cli():
29+
"""Local Business Extractor - Find businesses, extract contacts, sync to Sheets."""
30+
pass
31+
32+
33+
@cli.command()
34+
@click.option("--query", "-q", type=str, required=True, help="Search query (e.g. 'bakeries in San Francisco')")
35+
@click.option("--quiet", is_flag=True, help="Only output result JSON")
36+
@click.option("--verbose", "-v", is_flag=True, help="Show execution details")
37+
@click.option("--debug", is_flag=True, help="Show debug logging")
38+
def run(query, quiet, verbose, debug):
39+
"""Extract businesses matching a search query."""
40+
if not quiet:
41+
setup_logging(verbose=verbose, debug=debug)
42+
43+
context = {"user_request": query}
44+
45+
result = asyncio.run(default_agent.run(context))
46+
47+
output_data = {
48+
"success": result.success,
49+
"steps_executed": result.steps_executed,
50+
"output": result.output,
51+
}
52+
if result.error:
53+
output_data["error"] = result.error
54+
55+
click.echo(json.dumps(output_data, indent=2, default=str))
56+
sys.exit(0 if result.success else 1)
57+
58+
59+
@cli.command()
60+
@click.option("--json", "output_json", is_flag=True)
61+
def info(output_json):
62+
"""Show agent information."""
63+
info_data = default_agent.info()
64+
if output_json:
65+
click.echo(json.dumps(info_data, indent=2))
66+
else:
67+
click.echo(f"Agent: {info_data['name']}")
68+
click.echo(f"Version: {info_data['version']}")
69+
click.echo(f"Description: {info_data['description']}")
70+
click.echo(f"\nNodes: {', '.join(info_data['nodes'])}")
71+
click.echo(f"Entry: {info_data['entry_node']}")
72+
click.echo(f"Terminal: {', '.join(info_data['terminal_nodes'])}")
73+
74+
75+
@cli.command()
76+
def validate():
77+
"""Validate agent structure."""
78+
validation = default_agent.validate()
79+
if validation["valid"]:
80+
click.echo("Agent is valid")
81+
if validation["warnings"]:
82+
for warning in validation["warnings"]:
83+
click.echo(f" WARNING: {warning}")
84+
else:
85+
click.echo("Agent has errors:")
86+
for error in validation["errors"]:
87+
click.echo(f" ERROR: {error}")
88+
sys.exit(0 if validation["valid"] else 1)
89+
90+
91+
@cli.command()
92+
@click.option("--verbose", "-v", is_flag=True)
93+
def shell(verbose):
94+
"""Interactive session (CLI)."""
95+
asyncio.run(_interactive_shell(verbose))
96+
97+
98+
async def _interactive_shell(verbose=False):
99+
"""Async interactive shell."""
100+
setup_logging(verbose=verbose)
101+
102+
click.echo("=== Local Business Extractor ===")
103+
click.echo("Enter a search query (or 'quit' to exit):\n")
104+
105+
agent = LocalBusinessExtractor()
106+
await agent.start()
107+
108+
try:
109+
while True:
110+
try:
111+
query = await asyncio.get_event_loop().run_in_executor(
112+
None, input, "Query> "
113+
)
114+
if query.lower() in ["quit", "exit", "q"]:
115+
click.echo("Goodbye!")
116+
break
117+
118+
if not query.strip():
119+
continue
120+
121+
click.echo("\nExtracting...\n")
122+
123+
result = await agent.run({"user_request": query})
124+
125+
if result.success:
126+
click.echo(f"\nExtraction complete\n")
127+
else:
128+
click.echo(f"\nExtraction failed: {result.error}\n")
129+
130+
except KeyboardInterrupt:
131+
click.echo("\nGoodbye!")
132+
break
133+
except Exception as e:
134+
click.echo(f"Error: {e}", err=True)
135+
finally:
136+
await agent.stop()
137+
138+
139+
if __name__ == "__main__":
140+
cli()

0 commit comments

Comments
 (0)