LLM Agent tools: cover all terminal pages (hunt/portfolio/sniper/listing/inbox/yield/etc.)
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
This commit is contained in:
@ -2,18 +2,28 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any, Awaitable, Callable, Optional
|
||||
|
||||
from sqlalchemy import and_, func, select
|
||||
from sqlalchemy import and_, case, func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.tld_prices import get_trending_tlds
|
||||
from app.models.auction import DomainAuction
|
||||
from app.models.domain import Domain
|
||||
from app.models.listing import DomainListing, ListingStatus
|
||||
from app.models.listing import ListingInquiry, ListingInquiryMessage
|
||||
from app.models.portfolio import PortfolioDomain
|
||||
from app.models.sniper_alert import SniperAlert
|
||||
from app.models.subscription import Subscription, SubscriptionTier
|
||||
from app.models.user import User
|
||||
from app.models.yield_domain import YieldDomain, YieldTransaction
|
||||
from app.services.analyze.service import get_domain_analysis
|
||||
from app.services.domain_checker import domain_checker
|
||||
from app.services.hunt.brandables import check_domains, generate_cvcvc, generate_cvccv, generate_human
|
||||
from app.services.hunt.trends import fetch_google_trends_daily_rss
|
||||
from app.services.hunt.typos import generate_typos
|
||||
from app.services.zone_file import get_dropped_domains
|
||||
|
||||
|
||||
@ -144,6 +154,413 @@ async def tool_get_dashboard_summary(db: AsyncSession, user: User, args: dict[st
|
||||
}
|
||||
|
||||
|
||||
async def tool_hunt_trends(db: AsyncSession, user: User, args: dict[str, Any]) -> dict[str, Any]:
|
||||
geo = (args.get("geo") or "US").strip().upper()
|
||||
if len(geo) != 2:
|
||||
geo = "US"
|
||||
items_raw = await fetch_google_trends_daily_rss(geo=geo)
|
||||
return {
|
||||
"geo": geo,
|
||||
"items": [
|
||||
{
|
||||
"title": i["title"],
|
||||
"approx_traffic": i.get("approx_traffic"),
|
||||
"published_at": i.get("published_at"),
|
||||
"link": i.get("link"),
|
||||
}
|
||||
for i in items_raw[:50]
|
||||
],
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
}
|
||||
|
||||
|
||||
async def tool_hunt_brandables(db: AsyncSession, user: User, args: dict[str, Any]) -> dict[str, Any]:
|
||||
pattern = (args.get("pattern") or "cvcvc").strip().lower()
|
||||
if pattern not in ("cvcvc", "cvccv", "human"):
|
||||
pattern = "cvcvc"
|
||||
|
||||
tlds = args.get("tlds") or ["com"]
|
||||
if not isinstance(tlds, list):
|
||||
tlds = ["com"]
|
||||
tlds = [str(t).lower().lstrip(".") for t in tlds if str(t).strip()]
|
||||
if not tlds:
|
||||
tlds = ["com"]
|
||||
|
||||
max_checks = _clamp_int(args.get("max_checks"), lo=10, hi=80, default=40)
|
||||
limit = _clamp_int(args.get("limit"), lo=1, hi=25, default=15)
|
||||
|
||||
candidates: list[str] = []
|
||||
for _ in range(max_checks):
|
||||
if pattern == "cvcvc":
|
||||
sld = generate_cvcvc()
|
||||
elif pattern == "cvccv":
|
||||
sld = generate_cvccv()
|
||||
else:
|
||||
sld = generate_human()
|
||||
for t in tlds:
|
||||
candidates.append(f"{sld}.{t}")
|
||||
|
||||
checked = await check_domains(candidates, concurrency=30)
|
||||
available = [c for c in checked if c.status == "available"]
|
||||
seen = set()
|
||||
out = []
|
||||
for c in available:
|
||||
if c.domain in seen:
|
||||
continue
|
||||
seen.add(c.domain)
|
||||
out.append({"domain": c.domain, "status": c.status, "is_available": bool(c.is_available)})
|
||||
if len(out) >= limit:
|
||||
break
|
||||
|
||||
return {"pattern": pattern, "tlds": tlds, "items": out, "timestamp": datetime.utcnow().isoformat()}
|
||||
|
||||
|
||||
async def tool_hunt_typos(db: AsyncSession, user: User, args: dict[str, Any]) -> dict[str, Any]:
|
||||
brand = (args.get("brand") or "").strip()
|
||||
if not brand or len(brand) < 2:
|
||||
return {"error": "Missing brand"}
|
||||
limit = _clamp_int(args.get("limit"), lo=1, hi=30, default=15)
|
||||
tlds = args.get("tlds") or ["com"]
|
||||
if not isinstance(tlds, list):
|
||||
tlds = ["com"]
|
||||
tlds = [str(t).lower().lstrip(".") for t in tlds if str(t).strip()]
|
||||
if not tlds:
|
||||
tlds = ["com"]
|
||||
|
||||
# generate_typos returns SLD strings; we format domains for UX
|
||||
slugs = generate_typos(brand, limit=limit)
|
||||
items = []
|
||||
for sld in slugs:
|
||||
for t in tlds[:3]:
|
||||
items.append(f"{sld}.{t}")
|
||||
if len(items) >= limit:
|
||||
break
|
||||
if len(items) >= limit:
|
||||
break
|
||||
|
||||
return {"brand": brand, "items": items, "timestamp": datetime.utcnow().isoformat()}
|
||||
|
||||
|
||||
async def tool_keyword_availability(db: AsyncSession, user: User, args: dict[str, Any]) -> dict[str, Any]:
|
||||
keyword = (args.get("keyword") or "").strip().lower()
|
||||
if not keyword:
|
||||
return {"error": "Missing keyword"}
|
||||
tlds = args.get("tlds") or ["com"]
|
||||
if not isinstance(tlds, list):
|
||||
tlds = ["com"]
|
||||
tlds = [str(t).lower().lstrip(".") for t in tlds if str(t).strip()]
|
||||
if not tlds:
|
||||
tlds = ["com"]
|
||||
limit = _clamp_int(args.get("limit"), lo=1, hi=25, default=10)
|
||||
|
||||
candidates = [f"{keyword}.{t}" for t in tlds][:limit]
|
||||
results = []
|
||||
for d in candidates:
|
||||
r = await domain_checker.check_domain(d)
|
||||
results.append({"domain": d, "status": r.status, "is_available": bool(getattr(r, "is_available", False))})
|
||||
return {"keyword": keyword, "items": results, "timestamp": datetime.utcnow().isoformat()}
|
||||
|
||||
|
||||
async def tool_portfolio_summary(db: AsyncSession, user: User, args: dict[str, Any]) -> dict[str, Any]:
|
||||
rows = (await db.execute(select(PortfolioDomain).where(PortfolioDomain.user_id == user.id))).scalars().all()
|
||||
total = len(rows)
|
||||
active = sum(1 for d in rows if not d.is_sold and (d.status or "active") != "expired")
|
||||
sold = sum(1 for d in rows if bool(d.is_sold))
|
||||
total_cost = float(sum(Decimal(str(d.purchase_price or 0)) for d in rows))
|
||||
total_value = float(sum(Decimal(str(d.estimated_value or 0)) for d in rows if not d.is_sold))
|
||||
roi = None
|
||||
if total_cost > 0:
|
||||
roi = round(((total_value - total_cost) / total_cost) * 100, 2)
|
||||
return {
|
||||
"total_domains": total,
|
||||
"active_domains": active,
|
||||
"sold_domains": sold,
|
||||
"total_cost": round(total_cost, 2),
|
||||
"total_estimated_value": round(total_value, 2),
|
||||
"overall_roi_percent": roi,
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
}
|
||||
|
||||
|
||||
async def tool_list_portfolio(db: AsyncSession, user: User, args: dict[str, Any]) -> dict[str, Any]:
|
||||
limit = _clamp_int(args.get("limit"), lo=1, hi=50, default=25)
|
||||
status = (args.get("status") or "").strip().lower() or None
|
||||
q = select(PortfolioDomain).where(PortfolioDomain.user_id == user.id)
|
||||
if status:
|
||||
q = q.where(PortfolioDomain.status == status)
|
||||
q = q.order_by(PortfolioDomain.created_at.desc()).limit(limit)
|
||||
rows = (await db.execute(q)).scalars().all()
|
||||
return {
|
||||
"items": [
|
||||
{
|
||||
"id": d.id,
|
||||
"domain": d.domain,
|
||||
"status": d.status,
|
||||
"purchase_price": d.purchase_price,
|
||||
"estimated_value": d.estimated_value,
|
||||
"roi": d.roi,
|
||||
"renewal_date": d.renewal_date.isoformat() if d.renewal_date else None,
|
||||
"is_dns_verified": bool(getattr(d, "is_dns_verified", False) or False),
|
||||
}
|
||||
for d in rows
|
||||
],
|
||||
"count": len(rows),
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
}
|
||||
|
||||
|
||||
async def tool_list_sniper_alerts(db: AsyncSession, user: User, args: dict[str, Any]) -> dict[str, Any]:
|
||||
limit = _clamp_int(args.get("limit"), lo=1, hi=50, default=25)
|
||||
rows = (
|
||||
await db.execute(
|
||||
select(SniperAlert)
|
||||
.where(SniperAlert.user_id == user.id)
|
||||
.order_by(SniperAlert.created_at.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
).scalars().all()
|
||||
return {
|
||||
"items": [
|
||||
{
|
||||
"id": a.id,
|
||||
"name": a.name,
|
||||
"description": a.description,
|
||||
"is_active": bool(a.is_active),
|
||||
"tlds": a.tlds,
|
||||
"keywords": a.keywords,
|
||||
"exclude_keywords": a.exclude_keywords,
|
||||
"max_price": a.max_price,
|
||||
"ending_within_hours": a.ending_within_hours,
|
||||
"notify_email": bool(a.notify_email),
|
||||
"notify_sms": bool(a.notify_sms),
|
||||
"created_at": a.created_at.isoformat() if a.created_at else None,
|
||||
}
|
||||
for a in rows
|
||||
],
|
||||
"count": len(rows),
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
}
|
||||
|
||||
|
||||
async def tool_list_my_listings(db: AsyncSession, user: User, args: dict[str, Any]) -> dict[str, Any]:
|
||||
limit = _clamp_int(args.get("limit"), lo=1, hi=50, default=25)
|
||||
rows = (
|
||||
await db.execute(
|
||||
select(DomainListing)
|
||||
.where(DomainListing.user_id == user.id)
|
||||
.order_by(DomainListing.updated_at.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
).scalars().all()
|
||||
return {
|
||||
"items": [
|
||||
{
|
||||
"id": l.id,
|
||||
"domain": l.domain,
|
||||
"status": l.status,
|
||||
"asking_price": l.asking_price,
|
||||
"min_offer": l.min_offer,
|
||||
"currency": l.currency,
|
||||
"slug": l.slug,
|
||||
"verification_status": l.verification_status,
|
||||
"view_count": l.view_count,
|
||||
"inquiry_count": l.inquiry_count,
|
||||
"updated_at": l.updated_at.isoformat() if l.updated_at else None,
|
||||
}
|
||||
for l in rows
|
||||
],
|
||||
"count": len(rows),
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
}
|
||||
|
||||
|
||||
async def tool_get_inbox_counts(db: AsyncSession, user: User, args: dict[str, Any]) -> dict[str, Any]:
|
||||
buyer_inqs = (await db.execute(select(ListingInquiry.id).where(ListingInquiry.buyer_user_id == user.id))).scalars().all()
|
||||
buyer_unread = 0
|
||||
for inq_id in list(buyer_inqs)[:200]:
|
||||
msg = (
|
||||
await db.execute(
|
||||
select(ListingInquiryMessage)
|
||||
.where(ListingInquiryMessage.inquiry_id == inq_id)
|
||||
.order_by(ListingInquiryMessage.created_at.desc())
|
||||
.limit(1)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
if msg and msg.sender_user_id != user.id:
|
||||
buyer_unread += 1
|
||||
|
||||
listing_ids = (await db.execute(select(DomainListing.id).where(DomainListing.user_id == user.id))).scalars().all()
|
||||
seller_unread = 0
|
||||
if listing_ids:
|
||||
new_count = (
|
||||
await db.execute(
|
||||
select(func.count(ListingInquiry.id)).where(
|
||||
and_(ListingInquiry.listing_id.in_(listing_ids), ListingInquiry.status == "new")
|
||||
)
|
||||
)
|
||||
).scalar() or 0
|
||||
seller_unread += int(new_count)
|
||||
|
||||
inqs = (
|
||||
await db.execute(
|
||||
select(ListingInquiry.id, ListingInquiry.status)
|
||||
.where(
|
||||
and_(
|
||||
ListingInquiry.listing_id.in_(listing_ids),
|
||||
ListingInquiry.status.notin_(["closed", "spam"]),
|
||||
)
|
||||
)
|
||||
.order_by(ListingInquiry.created_at.desc())
|
||||
.limit(200)
|
||||
)
|
||||
).all()
|
||||
for inq_id, st in inqs:
|
||||
if st == "new":
|
||||
continue
|
||||
msg = (
|
||||
await db.execute(
|
||||
select(ListingInquiryMessage)
|
||||
.where(ListingInquiryMessage.inquiry_id == inq_id)
|
||||
.order_by(ListingInquiryMessage.created_at.desc())
|
||||
.limit(1)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
if msg and msg.sender_user_id != user.id:
|
||||
seller_unread += 1
|
||||
|
||||
return {"buyer_unread": buyer_unread, "seller_unread": seller_unread, "total_unread": buyer_unread + seller_unread}
|
||||
|
||||
|
||||
async def tool_get_seller_inbox(db: AsyncSession, user: User, args: dict[str, Any]) -> dict[str, Any]:
|
||||
limit = _clamp_int(args.get("limit"), lo=1, hi=50, default=25)
|
||||
status_filter = (args.get("status") or "all").strip().lower()
|
||||
|
||||
listings = (await db.execute(select(DomainListing).where(DomainListing.user_id == user.id))).scalars().all()
|
||||
listing_map = {l.id: l for l in listings}
|
||||
if not listing_map:
|
||||
return {"inquiries": [], "total": 0, "unread": 0}
|
||||
|
||||
q = (
|
||||
select(ListingInquiry)
|
||||
.where(ListingInquiry.listing_id.in_(list(listing_map.keys())))
|
||||
.order_by(ListingInquiry.created_at.desc())
|
||||
)
|
||||
if status_filter and status_filter != "all":
|
||||
q = q.where(ListingInquiry.status == status_filter)
|
||||
inqs = (await db.execute(q.limit(limit))).scalars().all()
|
||||
|
||||
unread = sum(1 for i in inqs if i.status == "new" or not i.read_at)
|
||||
items = []
|
||||
for inq in inqs:
|
||||
listing = listing_map.get(inq.listing_id)
|
||||
if not listing:
|
||||
continue
|
||||
latest_msg = (
|
||||
await db.execute(
|
||||
select(ListingInquiryMessage)
|
||||
.where(ListingInquiryMessage.inquiry_id == inq.id)
|
||||
.order_by(ListingInquiryMessage.created_at.desc())
|
||||
.limit(1)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
items.append(
|
||||
{
|
||||
"id": inq.id,
|
||||
"domain": listing.domain,
|
||||
"listing_id": listing.id,
|
||||
"slug": listing.slug,
|
||||
"status": inq.status,
|
||||
"buyer_name": inq.name,
|
||||
"offer_amount": inq.offer_amount,
|
||||
"created_at": inq.created_at.isoformat() if inq.created_at else None,
|
||||
"has_unread_reply": bool(latest_msg and latest_msg.sender_user_id != user.id and inq.status not in ["closed", "spam"]),
|
||||
"last_message_preview": (
|
||||
(latest_msg.body[:100] + "..." if len(latest_msg.body) > 100 else latest_msg.body)
|
||||
if latest_msg
|
||||
else ((inq.message or "")[:100])
|
||||
),
|
||||
"last_message_at": latest_msg.created_at.isoformat() if latest_msg and latest_msg.created_at else (inq.created_at.isoformat() if inq.created_at else None),
|
||||
}
|
||||
)
|
||||
|
||||
return {"inquiries": items, "total": len(items), "unread": unread, "timestamp": datetime.utcnow().isoformat()}
|
||||
|
||||
|
||||
async def tool_yield_dashboard(db: AsyncSession, user: User, args: dict[str, Any]) -> dict[str, Any]:
|
||||
now = datetime.utcnow()
|
||||
month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
domains = (
|
||||
await db.execute(
|
||||
select(YieldDomain)
|
||||
.where(YieldDomain.user_id == user.id)
|
||||
.order_by(YieldDomain.total_revenue.desc())
|
||||
)
|
||||
).scalars().all()
|
||||
domain_ids = [d.id for d in domains]
|
||||
|
||||
monthly_revenue = Decimal("0")
|
||||
monthly_clicks = 0
|
||||
monthly_conversions = 0
|
||||
if domain_ids:
|
||||
monthly_stats = (
|
||||
await db.execute(
|
||||
select(
|
||||
func.coalesce(
|
||||
func.sum(
|
||||
case(
|
||||
(YieldTransaction.status.in_(["confirmed", "paid"]), YieldTransaction.net_amount),
|
||||
else_=0,
|
||||
)
|
||||
),
|
||||
0,
|
||||
).label("revenue"),
|
||||
func.sum(case((YieldTransaction.event_type == "click", 1), else_=0)).label("clicks"),
|
||||
func.sum(
|
||||
case(
|
||||
(
|
||||
and_(
|
||||
YieldTransaction.event_type.in_(["lead", "sale"]),
|
||||
YieldTransaction.status.in_(["confirmed", "paid"]),
|
||||
),
|
||||
1,
|
||||
),
|
||||
else_=0,
|
||||
)
|
||||
).label("conversions"),
|
||||
).where(YieldTransaction.yield_domain_id.in_(domain_ids), YieldTransaction.created_at >= month_start)
|
||||
)
|
||||
).first()
|
||||
if monthly_stats:
|
||||
monthly_revenue = monthly_stats.revenue or Decimal("0")
|
||||
monthly_clicks = int(monthly_stats.clicks or 0)
|
||||
monthly_conversions = int(monthly_stats.conversions or 0)
|
||||
|
||||
return {
|
||||
"stats": {
|
||||
"active_domains": sum(1 for d in domains if d.status == "active"),
|
||||
"total_domains": len(domains),
|
||||
"monthly_revenue": float(monthly_revenue),
|
||||
"monthly_clicks": monthly_clicks,
|
||||
"monthly_conversions": monthly_conversions,
|
||||
},
|
||||
"top_domains": [
|
||||
{
|
||||
"id": d.id,
|
||||
"domain": d.domain,
|
||||
"status": d.status,
|
||||
"dns_verified": bool(d.dns_verified),
|
||||
"total_revenue": float(d.total_revenue or 0),
|
||||
"total_clicks": int(d.total_clicks or 0),
|
||||
"total_conversions": int(d.total_conversions or 0),
|
||||
"detected_intent": d.detected_intent,
|
||||
}
|
||||
for d in domains[:10]
|
||||
],
|
||||
"timestamp": now.isoformat(),
|
||||
}
|
||||
|
||||
|
||||
async def tool_list_watchlist(db: AsyncSession, user: User, args: dict[str, Any]) -> dict[str, Any]:
|
||||
page = _clamp_int(args.get("page"), lo=1, hi=50, default=1)
|
||||
per_page = _clamp_int(args.get("per_page"), lo=1, hi=50, default=20)
|
||||
@ -278,6 +695,65 @@ def get_tool_defs() -> list[ToolDef]:
|
||||
min_tier=SubscriptionTier.TRADER,
|
||||
handler=tool_get_dashboard_summary,
|
||||
),
|
||||
ToolDef(
|
||||
name="hunt_trends",
|
||||
description="Get Google Trends daily RSS items (Hunt > Trends).",
|
||||
json_schema={
|
||||
"type": "object",
|
||||
"properties": {"geo": {"type": "string", "minLength": 2, "maxLength": 2}},
|
||||
"additionalProperties": False,
|
||||
},
|
||||
min_tier=SubscriptionTier.TRADER,
|
||||
handler=tool_hunt_trends,
|
||||
),
|
||||
ToolDef(
|
||||
name="hunt_brandables",
|
||||
description="Generate brandable domains and check availability (Hunt > Forge).",
|
||||
json_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pattern": {"type": "string", "enum": ["cvcvc", "cvccv", "human"]},
|
||||
"tlds": {"type": "array", "items": {"type": "string"}},
|
||||
"max_checks": {"type": "integer", "minimum": 10, "maximum": 80},
|
||||
"limit": {"type": "integer", "minimum": 1, "maximum": 25},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
},
|
||||
min_tier=SubscriptionTier.TRADER,
|
||||
handler=tool_hunt_brandables,
|
||||
),
|
||||
ToolDef(
|
||||
name="hunt_typos",
|
||||
description="Generate typo candidates for a brand (Hunt > Typos).",
|
||||
json_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"brand": {"type": "string"},
|
||||
"tlds": {"type": "array", "items": {"type": "string"}},
|
||||
"limit": {"type": "integer", "minimum": 1, "maximum": 30},
|
||||
},
|
||||
"required": ["brand"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
min_tier=SubscriptionTier.TRADER,
|
||||
handler=tool_hunt_typos,
|
||||
),
|
||||
ToolDef(
|
||||
name="keyword_availability",
|
||||
description="Check availability for keyword + TLD list (Hunt > Search).",
|
||||
json_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"keyword": {"type": "string"},
|
||||
"tlds": {"type": "array", "items": {"type": "string"}},
|
||||
"limit": {"type": "integer", "minimum": 1, "maximum": 25},
|
||||
},
|
||||
"required": ["keyword"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
min_tier=SubscriptionTier.TRADER,
|
||||
handler=tool_keyword_availability,
|
||||
),
|
||||
ToolDef(
|
||||
name="list_watchlist",
|
||||
description="List user's watchlist domains (monitored domains).",
|
||||
@ -292,6 +768,70 @@ def get_tool_defs() -> list[ToolDef]:
|
||||
min_tier=SubscriptionTier.TRADER,
|
||||
handler=tool_list_watchlist,
|
||||
),
|
||||
ToolDef(
|
||||
name="portfolio_summary",
|
||||
description="Get portfolio summary (counts, total cost/value, ROI).",
|
||||
json_schema={"type": "object", "properties": {}, "additionalProperties": False},
|
||||
min_tier=SubscriptionTier.TRADER,
|
||||
handler=tool_portfolio_summary,
|
||||
),
|
||||
ToolDef(
|
||||
name="list_portfolio",
|
||||
description="List portfolio domains (owned domains).",
|
||||
json_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {"type": "string"},
|
||||
"limit": {"type": "integer", "minimum": 1, "maximum": 50},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
},
|
||||
min_tier=SubscriptionTier.TRADER,
|
||||
handler=tool_list_portfolio,
|
||||
),
|
||||
ToolDef(
|
||||
name="list_sniper_alerts",
|
||||
description="List sniper alerts (user-defined filters).",
|
||||
json_schema={
|
||||
"type": "object",
|
||||
"properties": {"limit": {"type": "integer", "minimum": 1, "maximum": 50}},
|
||||
"additionalProperties": False,
|
||||
},
|
||||
min_tier=SubscriptionTier.TRADER,
|
||||
handler=tool_list_sniper_alerts,
|
||||
),
|
||||
ToolDef(
|
||||
name="list_my_listings",
|
||||
description="List seller's Pounce Direct listings (For Sale).",
|
||||
json_schema={
|
||||
"type": "object",
|
||||
"properties": {"limit": {"type": "integer", "minimum": 1, "maximum": 50}},
|
||||
"additionalProperties": False,
|
||||
},
|
||||
min_tier=SubscriptionTier.TRADER,
|
||||
handler=tool_list_my_listings,
|
||||
),
|
||||
ToolDef(
|
||||
name="get_inbox_counts",
|
||||
description="Get unified buyer/seller unread counts for inbox badge.",
|
||||
json_schema={"type": "object", "properties": {}, "additionalProperties": False},
|
||||
min_tier=SubscriptionTier.TRADER,
|
||||
handler=tool_get_inbox_counts,
|
||||
),
|
||||
ToolDef(
|
||||
name="get_seller_inbox",
|
||||
description="Seller inbox threads across all listings (preview list).",
|
||||
json_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {"type": "string", "enum": ["all", "new", "read", "replied", "closed", "spam"]},
|
||||
"limit": {"type": "integer", "minimum": 1, "maximum": 50},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
},
|
||||
min_tier=SubscriptionTier.TRADER,
|
||||
handler=tool_get_seller_inbox,
|
||||
),
|
||||
ToolDef(
|
||||
name="analyze_domain",
|
||||
description="Run Pounce domain analysis (Authority/Market/Risk/Value) for a given domain.",
|
||||
@ -347,6 +887,13 @@ def get_tool_defs() -> list[ToolDef]:
|
||||
min_tier=SubscriptionTier.TRADER,
|
||||
handler=tool_get_drops,
|
||||
),
|
||||
ToolDef(
|
||||
name="yield_dashboard",
|
||||
description="Get Yield dashboard stats and top earning domains.",
|
||||
json_schema={"type": "object", "properties": {}, "additionalProperties": False},
|
||||
min_tier=SubscriptionTier.TRADER,
|
||||
handler=tool_yield_dashboard,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@ -357,11 +904,38 @@ def tools_for_path(path: str) -> list[str]:
|
||||
"""
|
||||
p = (path or "").split("?")[0]
|
||||
if p.startswith("/terminal/hunt"):
|
||||
return ["get_subscription", "get_dashboard_summary", "market_feed", "get_drops", "analyze_domain", "list_watchlist"]
|
||||
return [
|
||||
"get_subscription",
|
||||
"get_dashboard_summary",
|
||||
"market_feed",
|
||||
"get_drops",
|
||||
"hunt_trends",
|
||||
"hunt_brandables",
|
||||
"hunt_typos",
|
||||
"keyword_availability",
|
||||
"analyze_domain",
|
||||
"list_watchlist",
|
||||
]
|
||||
if p.startswith("/terminal/market"):
|
||||
return ["get_subscription", "market_feed", "analyze_domain"]
|
||||
if p.startswith("/terminal/watchlist"):
|
||||
return ["get_subscription", "list_watchlist", "analyze_domain"]
|
||||
if p.startswith("/terminal/portfolio"):
|
||||
return ["get_subscription", "portfolio_summary", "list_portfolio", "analyze_domain"]
|
||||
if p.startswith("/terminal/sniper"):
|
||||
return ["get_subscription", "list_sniper_alerts", "market_feed", "analyze_domain"]
|
||||
if p.startswith("/terminal/listing"):
|
||||
return ["get_subscription", "list_my_listings", "get_seller_inbox"]
|
||||
if p.startswith("/terminal/inbox"):
|
||||
return ["get_subscription", "get_inbox_counts", "get_seller_inbox"]
|
||||
if p.startswith("/terminal/yield"):
|
||||
return ["get_subscription", "yield_dashboard"]
|
||||
if p.startswith("/terminal/intel"):
|
||||
return ["get_subscription", "analyze_domain", "market_feed", "get_drops"]
|
||||
if p.startswith("/terminal/settings"):
|
||||
return ["get_subscription"]
|
||||
if p.startswith("/terminal/welcome"):
|
||||
return ["get_subscription", "get_dashboard_summary"]
|
||||
# default: allow a safe minimal set
|
||||
return ["get_subscription", "get_dashboard_summary", "analyze_domain"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user