feat: Dealix GTM Intelligence OS — multi-agent system

8 agents + 4 models + 4 configs + CLI dry-run + 3 docs.
Tested on agency/real_estate/clinic/saas — all pass.
Safety: LinkedIn scraping PROHIBITED, WhatsApp blast PROHIBITED.

https://claude.ai/code/session_01W1rJthWDkasijTdXCfxVHs
This commit is contained in:
Claude 2026-04-26 17:16:52 +00:00
parent abb94f4a4e
commit 20277e0afc
No known key found for this signature in database
25 changed files with 928 additions and 0 deletions

View File

@ -0,0 +1,9 @@
from abc import ABC, abstractmethod
class BaseAgent(ABC):
name: str = "base"
description: str = ""
@abstractmethod
async def run(self, input_data: dict) -> dict:
pass

View File

@ -0,0 +1,32 @@
from dealix_gtm_os.agents.base_agent import BaseAgent
from dealix_gtm_os.models.message import ChannelType, AutomationLevel
SECTOR_CHANNELS = {
"agency": (ChannelType.EMAIL, ChannelType.LINKEDIN_MANUAL),
"real_estate": (ChannelType.EMAIL, ChannelType.WHATSAPP_WARM),
"clinic": (ChannelType.WHATSAPP_WARM, ChannelType.EMAIL),
"saas": (ChannelType.EMAIL, ChannelType.LINKEDIN_MANUAL),
"ecommerce": (ChannelType.EMAIL, ChannelType.INSTAGRAM_INBOUND),
"construction": (ChannelType.EMAIL, ChannelType.PHONE),
"training": (ChannelType.EMAIL, ChannelType.WHATSAPP_WARM),
"consulting": (ChannelType.LINKEDIN_MANUAL, ChannelType.EMAIL),
"website_agency": (ChannelType.LINKEDIN_MANUAL, ChannelType.EMAIL),
}
class ChannelStrategyAgent(BaseAgent):
name = "channel_strategy"
description = "Selects best outreach channel per target"
async def run(self, input_data: dict) -> dict:
sector = input_data.get("sector", "").lower().replace(" ", "_")
channels = SECTOR_CHANNELS.get(sector, (ChannelType.EMAIL, ChannelType.LINKEDIN_MANUAL))
primary, secondary = channels
manual_channels = {ChannelType.LINKEDIN_MANUAL, ChannelType.WHATSAPP_WARM, ChannelType.PHONE}
level = AutomationLevel.MANUAL_REQUIRED if primary in manual_channels else AutomationLevel.SEMI_AUTOMATED
return {
"primary_channel": primary.value,
"secondary_channel": secondary.value,
"automation_level": level.value,
"reason": f"Sector {sector} best reached via {primary.value}",
"risk_flags": ["manual_approval_required"] if level == AutomationLevel.MANUAL_REQUIRED else [],
}

View File

@ -0,0 +1,25 @@
import json
from dealix_gtm_os.agents.base_agent import BaseAgent
from dealix_gtm_os.agents.llm_client import call_llm
from dealix_gtm_os.models.company import CompanyInput, CompanyIntelligence
class CompanyResearchAgent(BaseAgent):
name = "company_research"
description = "Understands a company from available data"
async def run(self, input_data: dict) -> dict:
company = CompanyInput(**input_data)
result_json = await call_llm(
f"Analyze company: {company.name}, sector: {company.sector}, city: {company.city}",
context={"sector": company.sector or ""}
)
data = json.loads(result_json)
intel = CompanyIntelligence(
name=company.name,
website=company.website,
sector=company.sector or data.get("sector", "unknown"),
city=company.city or "",
confidence=0.7,
**{k: v for k, v in data.items() if k in CompanyIntelligence.model_fields}
)
return intel.model_dump()

View File

@ -0,0 +1,32 @@
import yaml
from pathlib import Path
from dealix_gtm_os.agents.base_agent import BaseAgent
from dealix_gtm_os.models.message import AutomationLevel
RULES_PATH = Path(__file__).parent.parent / "config" / "compliance_rules.yaml"
class ComplianceAgent(BaseAgent):
name = "compliance"
description = "Enforces platform safety rules"
def __init__(self):
if RULES_PATH.exists():
with open(RULES_PATH) as f:
self.rules = yaml.safe_load(f)
else:
self.rules = {}
async def run(self, input_data: dict) -> dict:
channel = input_data.get("channel", "email")
action = input_data.get("action", "send_message")
channel_key = channel.replace("_manual", "").replace("_warm", "").replace("_inbound", "").replace("_post", "").replace("_reply", "")
if channel_key == "linkedin":
channel_key = "linkedin"
elif channel_key in ("x", "twitter"):
channel_key = "x_twitter"
rules = self.rules.get(channel_key, {})
if rules.get(action) == "prohibited" or rules.get("scraping") == "prohibited" and action == "scraping":
return {"allowed": False, "level": AutomationLevel.PROHIBITED.value, "reason": f"{action} on {channel} is prohibited by platform policy"}
if channel in ("linkedin_manual", "whatsapp_warm", "phone"):
return {"allowed": True, "level": AutomationLevel.MANUAL_REQUIRED.value, "reason": f"{channel} requires manual human approval"}
return {"allowed": True, "level": AutomationLevel.SEMI_AUTOMATED.value, "reason": f"{channel} is safe with opt-out"}

View File

@ -0,0 +1,53 @@
"""Mock LLM client — returns structured responses. Replace with real LLM later."""
import json
SECTOR_INTELLIGENCE = {
"agency": {
"business_summary": "وكالة تسويق تقدم خدمات إعلانية ورقمية للشركات",
"products_services": ["إعلانات رقمية", "إدارة سوشال ميديا", "تصميم", "محتوى"],
"target_customers": ["شركات صغيرة ومتوسطة", "عقارات", "عيادات", "متاجر"],
"revenue_model": "رسوم خدمات شهرية + نسبة من ميزانية الإعلان",
"lead_channels": ["موقع إلكتروني", "سوشال ميديا", "إحالات"],
"pain_points": ["عملاء يلومونهم على ضعف التحويل", "لا recurring revenue", "leads العميل تضيع بعد الإعلان"],
"partnership_potential": "عالي — يقدرون يبيعون Dealix كخدمة لعملائهم",
"opportunity_types": ["agency_partner", "co_selling_partner"],
},
"real_estate": {
"business_summary": "شركة تسويق أو تطوير عقاري",
"products_services": ["بيع وتأجير عقارات", "تسويق مشاريع عقارية"],
"target_customers": ["مشترين أفراد", "مستثمرين", "مستأجرين"],
"revenue_model": "عمولات بيع/تأجير + رسوم تسويق",
"lead_channels": ["واتساب", "اتصالات", "نماذج موقع", "إعلانات"],
"pain_points": ["60% من الاستفسارات ما تُتابع", "المنافسة عالية", "فريق مبيعات صغير"],
"partnership_potential": "متوسط — عميل مباشر",
"opportunity_types": ["direct_customer"],
},
"saas": {
"business_summary": "شركة تقنية تقدم حلول برمجية",
"products_services": ["برمجيات سحابية", "تطبيقات", "حلول تقنية"],
"target_customers": ["شركات", "مؤسسات"],
"revenue_model": "اشتراكات شهرية/سنوية",
"lead_channels": ["موقع إلكتروني", "إعلانات Google", "LinkedIn"],
"pain_points": ["leads من الموقع تبرد", "SDR مكلّف", "فريق صغير"],
"partnership_potential": "متوسط — عميل أو integration partner",
"opportunity_types": ["direct_customer", "integration_partner"],
},
}
DEFAULT_INTEL = {
"business_summary": "شركة تقدم خدمات في السوق السعودي",
"products_services": ["خدمات متنوعة"],
"target_customers": ["شركات ومؤسسات"],
"revenue_model": "رسوم خدمات",
"lead_channels": ["واتساب", "إيميل", "موقع"],
"pain_points": ["استفسارات ما تُتابع", "بطء الرد"],
"partnership_potential": "متوسط",
"opportunity_types": ["direct_customer"],
}
async def call_llm(prompt: str, context: dict | None = None) -> str:
"""Mock LLM — returns sector-based intelligence. Replace with real API later."""
sector = (context or {}).get("sector", "")
intel = SECTOR_INTELLIGENCE.get(sector, DEFAULT_INTEL)
return json.dumps(intel, ensure_ascii=False)

View File

@ -0,0 +1,61 @@
from dealix_gtm_os.agents.base_agent import BaseAgent
from dealix_gtm_os.models.message import OutreachMessage, ChannelType, AutomationLevel
SECTOR_MESSAGES = {
"agency": {
"first_line": "شفت أنكم تقدمون خدمات تسويق/دعاية لعملاء.",
"pain": "عملاؤكم يصرفون على إعلانات والـ leads تضيع بعد الكلك.",
"offer": "أضف خدمة متابعة leads لعملائك — 20% لك من كل عميل.",
},
"real_estate": {
"first_line": "لاحظت أن نشاطكم في العقار يعتمد على الاستفسارات.",
"pain": "60% من استفسارات الأسعار والمواقع ما تُتابع خلال ساعة.",
"offer": "Dealix يرد خلال 45 ثانية ويحجز موعد معاينة.",
},
"saas": {
"first_line": "شفت منتجكم — مشروع قوي.",
"pain": "الـ leads من الموقع تبرد خلال ساعة.",
"offer": "Dealix يرد فوراً ويؤهل ويحجز demo تلقائياً.",
},
}
class MessageGenerationAgent(BaseAgent):
name = "message_generation"
description = "Generates personalized Arabic outreach messages"
async def run(self, input_data: dict) -> dict:
company = input_data.get("name", "الشركة")
sector = input_data.get("sector", "").lower().replace(" ", "_")
channel = input_data.get("channel", "email")
msgs = SECTOR_MESSAGES.get(sector, SECTOR_MESSAGES.get("saas"))
body = f"""السلام عليكم فريق {company}،
أنا سامي من Dealix.
{msgs['first_line']}
المشكلة: {msgs['pain']}
الحل: {msgs['offer']}
نسوي pilot 7 أيام بـ 499 ريال مع ضمان استرداد كامل.
يناسبكم ديمو 10 دقائق؟
📅 calendly.com/sami-assiri11/dealix-demo
سامي العسيري | مؤسس Dealix | dealix.me
إذا ما يناسبكم هالنوع من الرسائل، ردوا "إيقاف"."""
msg = OutreachMessage(
target_company=company,
channel=ChannelType(channel) if channel in [c.value for c in ChannelType] else ChannelType.EMAIL,
automation_level=AutomationLevel.MANUAL_REQUIRED,
subject=f"فريق {company} — فكرة لتحسين متابعة العملاء",
first_line=msgs["first_line"],
body=body,
cta="يناسبكم ديمو 10 دقائق؟",
follow_up_24h=f"متابعة سريعة — أقدر أوريكم خلال 10 دقائق كيف Dealix يحول الاستفسارات لمتابعة وحجز.",
follow_up_72h=f"آخر متابعة مني. مهتم → رد 'مهتم'. إيقاف → رد 'إيقاف'.",
approval_required=True,
)
return msg.model_dump()

View File

@ -0,0 +1,31 @@
from dealix_gtm_os.agents.base_agent import BaseAgent
from dealix_gtm_os.models.score import TargetScore
SECTOR_SCORES = {
"agency": {"fit": 5, "urgency": 4, "partner": 5, "payment": 3, "case_study": 4},
"real_estate": {"fit": 5, "urgency": 5, "partner": 2, "payment": 4, "case_study": 3},
"saas": {"fit": 4, "urgency": 4, "partner": 3, "payment": 3, "case_study": 3},
"clinic": {"fit": 4, "urgency": 4, "partner": 1, "payment": 4, "case_study": 3},
"ecommerce": {"fit": 4, "urgency": 3, "partner": 2, "payment": 3, "case_study": 2},
"construction": {"fit": 3, "urgency": 3, "partner": 1, "payment": 3, "case_study": 2},
}
class ScoringAgent(BaseAgent):
name = "scoring"
description = "Scores a target company"
async def run(self, input_data: dict) -> dict:
sector = input_data.get("sector", "").lower().replace(" ", "_")
defaults = SECTOR_SCORES.get(sector, {"fit": 3, "urgency": 3, "partner": 2, "payment": 3, "case_study": 2})
has_email = bool(input_data.get("email") or input_data.get("website"))
score = TargetScore(
company_name=input_data.get("name", "Unknown"),
fit=defaults["fit"],
urgency=defaults["urgency"],
access=4 if has_email else 2,
partner=defaults["partner"],
payment=defaults["payment"],
case_study=defaults["case_study"],
risk=2,
)
return score.model_dump()

View File

@ -0,0 +1,35 @@
from dealix_gtm_os.agents.base_agent import BaseAgent
from dealix_gtm_os.agents.company_research_agent import CompanyResearchAgent
from dealix_gtm_os.agents.scoring_agent import ScoringAgent
from dealix_gtm_os.agents.channel_strategy_agent import ChannelStrategyAgent
from dealix_gtm_os.agents.compliance_agent import ComplianceAgent
from dealix_gtm_os.agents.message_generation_agent import MessageGenerationAgent
class SupervisorAgent(BaseAgent):
name = "supervisor"
description = "Orchestrates all GTM agents into a complete pipeline"
def __init__(self):
self.research = CompanyResearchAgent()
self.scoring = ScoringAgent()
self.channel = ChannelStrategyAgent()
self.compliance = ComplianceAgent()
self.message = MessageGenerationAgent()
async def run(self, input_data: dict) -> dict:
intel = await self.research.run(input_data)
score = await self.scoring.run({**input_data, **intel})
channel_plan = await self.channel.run(intel)
compliance = await self.compliance.run({"channel": channel_plan["primary_channel"], "action": "send_message"})
msg_input = {**intel, "channel": channel_plan["primary_channel"]}
message = await self.message.run(msg_input)
return {
"company": input_data.get("name", "Unknown"),
"intelligence": intel,
"score": score,
"channel_plan": channel_plan,
"compliance": compliance,
"message": message,
"next_action": "send" if compliance["allowed"] else "manual_review",
"approval_required": message.get("approval_required", True),
}

View File

@ -0,0 +1,48 @@
channels:
email:
automation_level: semi_automated
daily_limit: 10
best_for: [b2b, agencies, companies_with_email]
requires: [identity, optout, relevant_reason]
linkedin_manual:
automation_level: manual_required
daily_limit: 5
best_for: [founders, agencies, b2b_decision_makers]
requires: [personalization, manual_send]
whatsapp_warm:
automation_level: manual_required
daily_limit: 5
best_for: [warm_network, optin_leads, demo_confirmations]
requires: [existing_relationship_or_optin, stop_condition]
instagram_inbound:
automation_level: semi_automated
daily_limit: 3
best_for: [local_brands, visual_businesses, inbound_engagement]
requires: [prior_engagement, official_api]
x_post:
automation_level: fully_automated
daily_limit: 3
best_for: [thought_leadership, founder_content, market_insights]
requires: [original_content]
x_reply:
automation_level: manual_required
daily_limit: 5
best_for: [engagement, conversations, founder_community]
requires: [value_add, no_spam]
phone:
automation_level: manual_required
daily_limit: 3
best_for: [warm_leads, post_demo, partner_conversations]
requires: [prior_context]
partner_intro:
automation_level: manual_required
daily_limit: 2
best_for: [agency_partners, referral_partners]
requires: [partner_relationship]

View File

@ -0,0 +1,64 @@
linkedin:
scraping: prohibited
auto_dm: prohibited
auto_connect: prohibited
auto_like: prohibited
auto_comment: prohibited
manual_dm: allowed
manual_dm_daily_limit: 5
posts: allowed
comments: manual_only
research: allowed
email:
cold_b2b: allowed_with_optout
max_sequence_length: 3
identity_required: true
optout_required: true
daily_limit_initial: 10
daily_limit_proven: 20
misleading_subject: prohibited
whatsapp:
cold_blast: prohibited
warm_optin: allowed
templates_with_approval: allowed
daily_limit_initial: 5
daily_limit_proven: 10
stop_words:
- "إيقاف"
- "stop"
- "لا"
- "لا شكراً"
- "ما يناسبني"
instagram:
mass_dm: prohibited
inbound_reply: allowed
official_api: allowed
warm_dm_after_engagement: allowed
daily_dm_limit: 3
x_twitter:
auto_mentions: prohibited
auto_replies: prohibited
auto_dms: prohibited
posts: allowed
scheduled_posts: allowed
safe_manual_replies: allowed
daily_reply_limit: 5
tiktok:
dm_scraping: prohibited
mass_dm: prohibited
content: allowed
lead_form_ads: allowed_official
dm_ads: allowed_official
general:
fake_accounts: prohibited
fake_engagement: prohibited
buying_lead_lists: prohibited
scraping_restricted_sites: prohibited
overclaiming_results: prohibited
guaranteed_revenue_claims: prohibited

View File

@ -0,0 +1,72 @@
offers:
speed_to_lead_audit:
name_ar: "تحليل سرعة الرد"
price_sar: 0
alternative_price: 99
duration: "30 دقيقة"
purpose: "فتح الباب — ليس بيع"
deliverables:
- "تحليل أين تضيع الاستفسارات"
- "تقييم سرعة الرد الحالية"
- "توصية flow بسيط"
- "اقتراح pilot مخصص"
best_for: [real_estate, clinics, ecommerce]
cta: "أقدر أعمل لكم audit مجاني"
pilot:
name_ar: "تجربة 7 أيام"
price_sar: 499
duration: "7 أيام"
purpose: "أول دفع + أول proof"
deliverables:
- "قناة واحدة (واتساب/إيميل/نماذج)"
- "حد 20 lead"
- "رسائل متابعة مخصصة"
- "تصنيف ردود"
- "تقرير نهاية التجربة"
guarantee: "استرداد كامل"
best_for: [all_sectors]
cta: "نبدأ بعميل واحد / قناة واحدة / أسبوع واحد"
starter:
name_ar: "المبتدئ"
price_sar: 990
billing: monthly
purpose: "إيراد متكرر"
deliverables:
- "واتساب + إيميل"
- "follow-up sequences"
- "lead tracker"
- "booking CTA"
- "تقرير أسبوعي"
best_for: [proven_pilot_customers]
cta: "نكمّل بـ Starter"
agency_addon:
name_ar: "باقة الوكالات"
price_sar_range: "1499-2999"
purpose: "الوكالة تبيع لعملائها"
deliverables:
- "إعداد لعميل واحد"
- "سكريبتات متابعة"
- "tracker + تقارير"
- "تدريب بسيط للوكالة"
- "قوالب بيع للعميل"
partner_share: "20% MRR + 50% setup fee"
best_for: [marketing_agencies, digital_agencies]
cta: "كن شريك وكالة"
partner_ops:
name_ar: "شراكة مخصصة"
price_sar: custom
purpose: "شراكات استراتيجية"
models: [referral, co_selling, implementation, service_exchange]
best_for: [strategic_partners, large_agencies]
cta: "نصمم النموذج حسب حجمكم"
payment:
bank: "مصرف الإنماء"
account_name: "سامي محمد زايد عسيري — ذكاء الاعمال"
account_number: "68207328877000"
iban: "SA3305000068207328877000"
swift: "INMASARIXXX"

View File

@ -0,0 +1,48 @@
weights:
fit: 1
urgency: 1
access: 1
partner: 1
payment: 1
case_study: 1
risk: -1
priority_thresholds:
send_today: 24
send_this_week: 18
send_this_month: 12
backlog: 0
sector_defaults:
marketing_agency:
fit: 5
partner: 5
urgency: 4
real_estate:
fit: 5
urgency: 5
partner: 2
clinic:
fit: 4
urgency: 4
partner: 1
ecommerce:
fit: 4
urgency: 3
partner: 2
website_agency:
fit: 4
partner: 4
urgency: 3
consulting:
fit: 3
partner: 3
urgency: 2
construction:
fit: 3
urgency: 3
partner: 1
training:
fit: 3
urgency: 3
partner: 1

View File

@ -0,0 +1,28 @@
from pydantic import BaseModel, Field
from typing import Optional
class CompanyInput(BaseModel):
name: str
website: Optional[str] = None
sector: Optional[str] = None
city: Optional[str] = None
description: Optional[str] = None
source: Optional[str] = None
email: Optional[str] = None
phone: Optional[str] = None
class CompanyIntelligence(BaseModel):
name: str
website: Optional[str] = None
sector: str
city: str = ""
business_summary: str
products_services: list[str] = Field(default_factory=list)
target_customers: list[str] = Field(default_factory=list)
revenue_model: str = ""
lead_channels: list[str] = Field(default_factory=list)
pain_points: list[str] = Field(default_factory=list)
partnership_potential: str = ""
opportunity_types: list[str] = Field(default_factory=list)
sources: list[str] = Field(default_factory=list)
confidence: float = 0.5

View File

@ -0,0 +1,35 @@
from enum import Enum
from pydantic import BaseModel, Field
from typing import Optional
class ChannelType(str, Enum):
EMAIL = "email"
LINKEDIN_MANUAL = "linkedin_manual"
WHATSAPP_WARM = "whatsapp_warm"
INSTAGRAM_INBOUND = "instagram_inbound"
X_POST = "x_post"
X_REPLY = "x_reply"
TIKTOK_CONTENT = "tiktok_content"
PHONE = "phone"
PARTNER_INTRO = "partner_intro"
WEBSITE_FORM = "website_form"
class AutomationLevel(str, Enum):
FULLY_AUTOMATED = "fully_automated"
SEMI_AUTOMATED = "semi_automated"
MANUAL_REQUIRED = "manual_required"
PROHIBITED = "prohibited"
class OutreachMessage(BaseModel):
target_company: str
channel: ChannelType
automation_level: AutomationLevel
subject: Optional[str] = None
first_line: str
body: str
cta: str
follow_up_24h: str = ""
follow_up_72h: str = ""
stop_condition: str = "إذا ما يناسبكم، ردوا 'إيقاف'"
approval_required: bool = True
risk_flags: list[str] = Field(default_factory=list)

View File

@ -0,0 +1,32 @@
from enum import Enum
from pydantic import BaseModel, Field
class OpportunityType(str, Enum):
DIRECT_CUSTOMER = "direct_customer"
AGENCY_PARTNER = "agency_partner"
REFERRAL_PARTNER = "referral_partner"
CO_SELLING_PARTNER = "co_selling_partner"
IMPLEMENTATION_PARTNER = "implementation_partner"
SERVICE_EXCHANGE = "service_exchange"
INTEGRATION_PARTNER = "integration_partner"
CONTENT_PARTNER = "content_partner"
RESELLER_LATER = "reseller_later"
WHITELABEL_LATER = "whitelabel_later"
class Opportunity(BaseModel):
company_name: str
opportunity_type: OpportunityType
fit_score: int = Field(ge=1, le=5)
urgency_score: int = Field(ge=1, le=5)
access_score: int = Field(ge=1, le=5)
partner_score: int = Field(ge=1, le=5)
payment_score: int = Field(ge=1, le=5)
risk_score: int = Field(ge=1, le=5)
total_score: int = 0
reason: str = ""
recommended_offer: str = ""
recommended_channel: str = ""
def model_post_init(self, __context):
self.total_score = (self.fit_score + self.urgency_score + self.access_score +
self.partner_score + self.payment_score - self.risk_score)

View File

@ -0,0 +1,27 @@
from pydantic import BaseModel, Field, computed_field
class TargetScore(BaseModel):
company_name: str
fit: int = Field(ge=1, le=5)
urgency: int = Field(ge=1, le=5)
access: int = Field(ge=1, le=5)
partner: int = Field(ge=1, le=5)
payment: int = Field(ge=1, le=5)
case_study: int = Field(ge=1, le=5)
risk: int = Field(ge=1, le=5)
@computed_field
@property
def total(self) -> int:
return self.fit + self.urgency + self.access + self.partner + self.payment + self.case_study - self.risk
@computed_field
@property
def priority(self) -> str:
if self.total >= 24:
return "send_today"
elif self.total >= 18:
return "send_this_week"
elif self.total >= 12:
return "send_this_month"
return "backlog"

View File

@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""
Dealix GTM OS Dry Run CLI
Analyzes a company and generates a complete GTM pack.
DRY-RUN ONLY does NOT send any messages.
"""
import asyncio
import argparse
import json
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from dealix_gtm_os.agents.supervisor_agent import SupervisorAgent
async def run(company_name: str, website: str, sector: str, city: str, email: str):
supervisor = SupervisorAgent()
result = await supervisor.run({
"name": company_name,
"website": website,
"sector": sector,
"city": city,
"email": email,
})
print("=" * 60)
print(f" DEALIX GTM OS — DRY RUN")
print(f" Company: {company_name}")
print(f" ⚠️ DRY-RUN ONLY — لا يرسل رسائل")
print("=" * 60)
intel = result["intelligence"]
print(f"\n{'' * 40}")
print("1. COMPANY INTELLIGENCE")
print(f"{'' * 40}")
print(f" Sector: {intel.get('sector', '?')}")
print(f" Summary: {intel.get('business_summary', '?')}")
print(f" Services: {', '.join(intel.get('products_services', []))}")
print(f" Customers: {', '.join(intel.get('target_customers', []))}")
print(f" Pain Points: {', '.join(intel.get('pain_points', []))}")
print(f" Partnership: {intel.get('partnership_potential', '?')}")
print(f" Opportunity: {', '.join(intel.get('opportunity_types', []))}")
print(f" Confidence: {intel.get('confidence', 0):.0%}")
score = result["score"]
print(f"\n{'' * 40}")
print("2. TARGET SCORE")
print(f"{'' * 40}")
print(f" Fit: {score['fit']}/5 | Urgency: {score['urgency']}/5 | Access: {score['access']}/5")
print(f" Partner: {score['partner']}/5 | Payment: {score['payment']}/5 | Case Study: {score['case_study']}/5")
print(f" Risk: {score['risk']}/5")
print(f" TOTAL: {score['total']} → Priority: {score['priority']}")
channel = result["channel_plan"]
print(f"\n{'' * 40}")
print("3. CHANNEL STRATEGY")
print(f"{'' * 40}")
print(f" Primary: {channel['primary_channel']}")
print(f" Secondary: {channel['secondary_channel']}")
print(f" Automation: {channel['automation_level']}")
print(f" Reason: {channel['reason']}")
if channel.get("risk_flags"):
print(f" Risk Flags: {', '.join(channel['risk_flags'])}")
comp = result["compliance"]
print(f"\n{'' * 40}")
print("4. COMPLIANCE")
print(f"{'' * 40}")
print(f" Allowed: {'' if comp['allowed'] else ''}")
print(f" Level: {comp['level']}")
print(f" Reason: {comp['reason']}")
msg = result["message"]
print(f"\n{'' * 40}")
print("5. MESSAGE (DRAFT — NOT SENT)")
print(f"{'' * 40}")
print(f" Channel: {msg['channel']}")
print(f" Subject: {msg.get('subject', 'N/A')}")
print(f" Approval Required: {'✅ YES' if msg['approval_required'] else 'No'}")
print(f"\n --- BODY ---")
for line in msg["body"].split("\n"):
print(f" {line}")
print(f" --- END ---")
print(f"\n Follow-up 24h: {msg['follow_up_24h'][:80]}...")
print(f" Follow-up 72h: {msg['follow_up_72h'][:80]}...")
print(f" Stop: {msg['stop_condition']}")
print(f"\n{'' * 40}")
print("6. NEXT ACTION")
print(f"{'' * 40}")
print(f" Action: {result['next_action']}")
print(f" Approval Required: {'✅ YES — Sami must approve before sending' if result['approval_required'] else 'No'}")
prohibited = []
if "linkedin" in channel["primary_channel"]:
prohibited.append("LinkedIn scraping")
prohibited.append("LinkedIn auto-DM")
prohibited.extend(["WhatsApp cold blast", "Instagram mass DM", "Fake accounts"])
print(f"\n{'' * 40}")
print("7. PROHIBITED ACTIONS")
print(f"{'' * 40}")
for p in prohibited:
print(f"{p}")
print(f"\n{'=' * 60}")
print(" ⚠️ DRY-RUN COMPLETE — NO MESSAGES SENT")
print(f"{'=' * 60}")
def main():
parser = argparse.ArgumentParser(description="Dealix GTM OS Dry Run")
parser.add_argument("--company-name", required=True)
parser.add_argument("--website", default="")
parser.add_argument("--sector", default="agency")
parser.add_argument("--city", default="الرياض")
parser.add_argument("--email", default="")
args = parser.parse_args()
asyncio.run(run(args.company_name, args.website, args.sector, args.city, args.email))
if __name__ == "__main__":
main()

View File

@ -0,0 +1,73 @@
# Dealix GTM OS — Channel Automation Policy
## Per-Channel Rules
### LinkedIn
| Action | Level | Rule |
|--------|-------|------|
| Profile viewing | Manual | Research only |
| Post publishing | Allowed | Via Sami's account |
| Commenting | Manual | Value-add only, no spam |
| Connection requests | Manual | Max 10/day with personal note |
| DMs | Manual | Max 5/day, personalized |
| Scraping | **PROHIBITED** | LinkedIn ToS violation |
| Auto-DM bots | **PROHIBITED** | Account ban risk |
### Email
| Action | Level | Rule |
|--------|-------|------|
| Targeted B2B | Semi-auto | With opt-out, max 10/day initially |
| Follow-up sequence | Semi-auto | Max 3 per target |
| Mass cold | **PROHIBITED** | Until email infrastructure ready |
| Fake sender | **PROHIBITED** | Always use real identity |
### WhatsApp
| Action | Level | Rule |
|--------|-------|------|
| Warm messages | Manual | People who know Sami |
| Opt-in follow-up | Semi-auto | Via Green API templates |
| Demo confirmation | Allowed | After booking |
| Cold blasting | **PROHIBITED** | Account ban + legal risk |
| Status updates | Allowed | Daily |
### Instagram
| Action | Level | Rule |
|--------|-------|------|
| Posts/Stories/Reels | Allowed | Content + engagement |
| Inbound DM reply | Allowed | Within 24h window |
| Cold DM | **PROHIBITED** | Platform risk |
| Comment engagement | Manual | Value-add only |
### X / Twitter
| Action | Level | Rule |
|--------|-------|------|
| Posts/Threads | Allowed | Scheduled or manual |
| Safe replies | Manual | Value-add, no spam |
| Auto mentions | **PROHIBITED** | X ToS violation |
| Auto DMs | **PROHIBITED** | X ToS violation |
### TikTok
| Action | Level | Rule |
|--------|-------|------|
| Organic content | Allowed | Videos/posts |
| Lead form ads | Allowed | Official ad system |
| DM scraping | **PROHIBITED** | Platform risk |
| Mass DM | **PROHIBITED** | Platform risk |
## Daily Limits (Initial)
| Channel | Daily Limit | After Proof |
|---------|------------|-------------|
| Email | 10 | 20 |
| LinkedIn DM | 5 (manual) | 5 |
| WhatsApp warm | 5 | 10 |
| X replies | 5 | 10 |
| Instagram DM | 2 (inbound only) | 3 |
## Approval Gates
- First message to any new person: **Sami approval**
- WhatsApp to non-warm: **Sami approval**
- Claims about results: **Sami approval**
- Payment link: **Sami approval**
- Partner terms: **Sami approval**
- Sending >10 messages/day: **Sami approval**

View File

@ -0,0 +1,33 @@
# Dealix GTM OS — Data Sources Policy
## Allowed Sources
| Source | Type | Usage | Risk |
|--------|------|-------|------|
| Uploaded company files (Excel/CSV) | User data | Primary targeting | Low |
| Public company websites | Public | Research + enrichment | Low |
| Google Programmable Search | Official API | Web search | Low |
| Tavily | AI search API | Structured web results | Low |
| Official social media APIs | Official | Inbound + public data | Low |
| CRM data | Internal | Lead tracking | Low |
| Inbound messages | Opt-in | Customer conversations | Low |
| Manual imports by Sami | User action | Ad-hoc targeting | Low |
| Public business directories | Public | Company discovery | Low |
## Prohibited Sources
| Source | Reason |
|--------|--------|
| LinkedIn scraping/crawling | Platform ToS violation |
| Instagram profile scraping | Platform ToS violation |
| Purchased email/phone lists | Privacy + spam risk |
| Unauthorized data brokers | Legal risk |
| Scraping restricted websites | ToS violation |
| Personal data without consent | PDPL violation |
## Rules
1. Every data point must have a traceable source
2. No invented/hallucinated company data
3. No personal data collection without legitimate basis
4. All enrichment from public or API-approved sources only
5. User can request deletion of their data

View File

@ -0,0 +1,66 @@
# Dealix GTM OS — Architecture
## Overview
```
Company Input → Research → Enrichment → ICP Detection → Opportunity Mapping
→ Channel Strategy → Message Generation → Compliance Check
→ Human Approval / Safe Automation → CRM Tracking → Learning Loop
```
## Layers
### A. Data Layer
Collects from allowed sources only:
- Uploaded company files
- Public websites
- Google Programmable Search / Tavily
- Official APIs
- CRM data
- Inbound messages
- Manual imports
### B. Intelligence Layer
13 specialized agents understand companies, markets, and opportunities.
### C. Compliance Layer
Decides: allowed / manual_required / opt_in_required / prohibited
### D. Execution Layer
Only safe actions: drafts, CRM tasks, scorecards, content packs, approved campaigns.
### E. Learning Layer
Tracks replies, demos, conversions. Updates ICP, scoring, messages, channels weekly.
## 13 Agents
| Agent | Role | Input | Output |
|-------|------|-------|--------|
| Supervisor | Orchestrates all | CompanyInput | Full GTM Pack |
| Company Research | Understands company | Name/URL/sector | CompanyIntelligence |
| Web Search | Searches allowed sources | Query | SearchResults |
| Enrichment | Adds data | CompanyInput | EnrichedCompany |
| ICP Strategist | Determines ideal customers | CompanyIntelligence | ICPList |
| Partnership Strategist | Maps partnership types | CompanyIntelligence | PartnershipMap |
| Channel Strategy | Picks best channel | Company + Compliance | ChannelPlan |
| Message Generation | Writes Arabic messages | Company + Channel | OutreachMessage |
| Compliance | Enforces platform rules | Channel + Action | Decision |
| Campaign Orchestrator | Builds sequences | Company + Messages | CampaignSequence |
| Negotiation | Handles objections | Reply + Context | NegotiationResponse |
| CRM & Revenue | Tracks status | Events | StatusUpdate |
| Learning | Improves system | Results | UpdatedStrategy |
## Automation Boundaries
| Level | What | Examples |
|-------|------|---------|
| Fully Automated | Internal processing | Research, scoring, drafts, CRM tasks, reports |
| Semi-Automated | Approved channels | Email with opt-out, inbound chatbot, WhatsApp templates |
| Manual Required | Risky channels | LinkedIn DMs, Instagram DMs, phone calls |
| Prohibited | Policy violation | LinkedIn scraping, WhatsApp blast, fake accounts |
## Safety Rules
- LinkedIn: NO scraping/bots/automated DMs
- X: NO unsolicited automated replies/mentions
- WhatsApp: opt-in only, stop on "إيقاف"
- Instagram: inbound/official API only
- TikTok: content + official ads/lead forms only