@mgsoftwarebv/mg-dashboard-mcp 2.4.2 → 2.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +268 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -388,7 +388,12 @@ var VALID_FINDING_TYPES = /* @__PURE__ */ new Set([
|
|
|
388
388
|
"slow_endpoint",
|
|
389
389
|
"memory_leak",
|
|
390
390
|
"missing_memoization",
|
|
391
|
-
"large_dependency"
|
|
391
|
+
"large_dependency",
|
|
392
|
+
"dead_code",
|
|
393
|
+
"unused_dependency",
|
|
394
|
+
"env_leak",
|
|
395
|
+
"i18n_missing_key",
|
|
396
|
+
"i18n_unused_key"
|
|
392
397
|
]);
|
|
393
398
|
var VALID_SEVERITIES = /* @__PURE__ */ new Set(["info", "warning", "critical"]);
|
|
394
399
|
var VALID_SCOPES = /* @__PURE__ */ new Set([
|
|
@@ -398,7 +403,9 @@ var VALID_SCOPES = /* @__PURE__ */ new Set([
|
|
|
398
403
|
"api",
|
|
399
404
|
"package",
|
|
400
405
|
"security",
|
|
401
|
-
"server_audit"
|
|
406
|
+
"server_audit",
|
|
407
|
+
"changelog",
|
|
408
|
+
"api-reference"
|
|
402
409
|
]);
|
|
403
410
|
var VALID_REVIEW_STATUSES = /* @__PURE__ */ new Set([
|
|
404
411
|
"pending",
|
|
@@ -406,6 +413,9 @@ var VALID_REVIEW_STATUSES = /* @__PURE__ */ new Set([
|
|
|
406
413
|
"agent_flagged",
|
|
407
414
|
"human_approved"
|
|
408
415
|
]);
|
|
416
|
+
function normalizeCompanyName(name) {
|
|
417
|
+
return name.toLowerCase().replace(/\b(b\.?v\.?|n\.?v\.?|v\.?o\.?f\.?|c\.?v\.?|holding|groep|group|nederland|netherlands)\b/gi, "").replace(/[^a-z0-9\s]/g, "").replace(/\s+/g, " ").trim();
|
|
418
|
+
}
|
|
409
419
|
var AGENT_TOOLS = [
|
|
410
420
|
{
|
|
411
421
|
name: "agent-report-coverage",
|
|
@@ -439,7 +449,7 @@ var AGENT_TOOLS = [
|
|
|
439
449
|
}
|
|
440
450
|
}
|
|
441
451
|
},
|
|
442
|
-
required: ["repo_slug", "
|
|
452
|
+
required: ["repo_slug", "entries"]
|
|
443
453
|
}
|
|
444
454
|
},
|
|
445
455
|
{
|
|
@@ -494,7 +504,7 @@ var AGENT_TOOLS = [
|
|
|
494
504
|
}
|
|
495
505
|
}
|
|
496
506
|
},
|
|
497
|
-
required: ["repo_slug", "
|
|
507
|
+
required: ["repo_slug", "category", "findings"]
|
|
498
508
|
}
|
|
499
509
|
},
|
|
500
510
|
{
|
|
@@ -504,7 +514,7 @@ var AGENT_TOOLS = [
|
|
|
504
514
|
type: "object",
|
|
505
515
|
properties: {
|
|
506
516
|
repo_slug: { type: "string", description: "Repository slug" },
|
|
507
|
-
refront_project_id: { type: "string", description: "Refront project UUID" },
|
|
517
|
+
refront_project_id: { type: "string", description: "Refront project UUID (optional for standalone repos)" },
|
|
508
518
|
scope: {
|
|
509
519
|
type: "string",
|
|
510
520
|
enum: [...VALID_SCOPES],
|
|
@@ -522,7 +532,7 @@ var AGENT_TOOLS = [
|
|
|
522
532
|
description: 'Review status (default: "pending")'
|
|
523
533
|
}
|
|
524
534
|
},
|
|
525
|
-
required: ["repo_slug", "
|
|
535
|
+
required: ["repo_slug", "scope", "path", "title", "content"]
|
|
526
536
|
}
|
|
527
537
|
},
|
|
528
538
|
{
|
|
@@ -578,7 +588,7 @@ var AGENT_TOOLS = [
|
|
|
578
588
|
},
|
|
579
589
|
{
|
|
580
590
|
name: "agent-validate-suggestions",
|
|
581
|
-
description: "Validate existing open suggestions against the actual codebase. For each suggestion, report whether it is valid, invalid (should be dismissed), or needs adjustment.
|
|
591
|
+
description: "Validate existing open suggestions against the actual codebase. For each suggestion, report whether it is resolved (fixed), valid (still open), invalid (should be dismissed), or needs adjustment.",
|
|
582
592
|
inputSchema: {
|
|
583
593
|
type: "object",
|
|
584
594
|
properties: {
|
|
@@ -598,8 +608,8 @@ var AGENT_TOOLS = [
|
|
|
598
608
|
},
|
|
599
609
|
verdict: {
|
|
600
610
|
type: "string",
|
|
601
|
-
enum: ["valid", "invalid", "adjusted"],
|
|
602
|
-
description: "
|
|
611
|
+
enum: ["valid", "invalid", "adjusted", "resolved"],
|
|
612
|
+
description: "resolved = fix applied (sets status resolved), valid = still relevant (keep open), invalid = dismiss, adjusted = update fields"
|
|
603
613
|
},
|
|
604
614
|
reason: {
|
|
605
615
|
type: "string",
|
|
@@ -625,6 +635,79 @@ var AGENT_TOOLS = [
|
|
|
625
635
|
},
|
|
626
636
|
required: ["repo_slug", "results"]
|
|
627
637
|
}
|
|
638
|
+
},
|
|
639
|
+
// -- Lead Generation tools --------------------------------------------------
|
|
640
|
+
{
|
|
641
|
+
name: "agent-check-lead-exists",
|
|
642
|
+
description: "Check if a lead already exists by website URL or company name. Call this BEFORE visiting a website to avoid wasting time on duplicates.",
|
|
643
|
+
inputSchema: {
|
|
644
|
+
type: "object",
|
|
645
|
+
properties: {
|
|
646
|
+
website_url: {
|
|
647
|
+
type: "string",
|
|
648
|
+
description: 'Website URL to check (e.g. "https://example.nl")'
|
|
649
|
+
},
|
|
650
|
+
company_name: {
|
|
651
|
+
type: "string",
|
|
652
|
+
description: "Company name to fuzzy-match against existing leads"
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
required: []
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
name: "agent-save-lead",
|
|
660
|
+
description: "Save a discovered lead (company) to the database. Handles dedup on website_url + fuzzy match on company name. Returns the lead ID and whether it already existed.",
|
|
661
|
+
inputSchema: {
|
|
662
|
+
type: "object",
|
|
663
|
+
properties: {
|
|
664
|
+
company_name: { type: "string", description: "Company name" },
|
|
665
|
+
website_url: { type: "string", description: "Company website URL" },
|
|
666
|
+
industry: { type: "string", description: "Industry sector" },
|
|
667
|
+
region: { type: "string", description: 'Geographic region (e.g. "amsterdam", "rotterdam")' },
|
|
668
|
+
description: { type: "string", description: "AI-generated summary of what the company does (max 2000 chars)" },
|
|
669
|
+
potential_fit: { type: "string", description: "Why MG Software could help this company (max 2000 chars)" },
|
|
670
|
+
fit_score: { type: "number", description: "Fit score 1-10 for MG Software partnership" },
|
|
671
|
+
estimated_company_size: { type: "string", description: 'Estimated employee count range (e.g. "1-10", "10-50", "50-200")' },
|
|
672
|
+
kvk_number: { type: "string", description: "KvK (Chamber of Commerce) number if found" },
|
|
673
|
+
contact_name: { type: "string", description: "Primary contact person name" },
|
|
674
|
+
contact_role: { type: "string", description: "Contact person role/title" },
|
|
675
|
+
contact_email: { type: "string", description: "Contact person email" },
|
|
676
|
+
contact_phone: { type: "string", description: "Contact person phone" },
|
|
677
|
+
contact_linkedin: { type: "string", description: "Contact person LinkedIn URL" },
|
|
678
|
+
general_email: { type: "string", description: "General company email (info@...)" },
|
|
679
|
+
general_phone: { type: "string", description: "General company phone number" },
|
|
680
|
+
source_url: { type: "string", description: "URL where this lead was found (Google result, directory, etc.)" },
|
|
681
|
+
target_id: { type: "string", description: "UUID of the lead_generation_target this lead was found for" }
|
|
682
|
+
},
|
|
683
|
+
required: ["company_name", "website_url"]
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
name: "agent-save-email-draft",
|
|
688
|
+
description: "Save a cold email draft for a lead. The email will be shown in the backoffice for manual review and copy. Do NOT use em-dashes or en-dashes in the email.",
|
|
689
|
+
inputSchema: {
|
|
690
|
+
type: "object",
|
|
691
|
+
properties: {
|
|
692
|
+
lead_id: { type: "string", description: "UUID of the lead this email is for" },
|
|
693
|
+
subject: { type: "string", description: "Email subject line (Dutch, max 200 chars)" },
|
|
694
|
+
body: { type: "string", description: "Email body text (Dutch, max 5000 chars, NO em-dashes or en-dashes)" },
|
|
695
|
+
tone: { type: "string", description: "Tone of the email (default: professional)" }
|
|
696
|
+
},
|
|
697
|
+
required: ["lead_id", "subject", "body"]
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
name: "agent-complete-target",
|
|
702
|
+
description: "Mark a lead_generation_target as completed with the number of leads found.",
|
|
703
|
+
inputSchema: {
|
|
704
|
+
type: "object",
|
|
705
|
+
properties: {
|
|
706
|
+
target_id: { type: "string", description: "UUID of the lead_generation_target" },
|
|
707
|
+
results_count: { type: "number", description: "Number of new leads saved for this target" }
|
|
708
|
+
},
|
|
709
|
+
required: ["target_id", "results_count"]
|
|
710
|
+
}
|
|
628
711
|
}
|
|
629
712
|
];
|
|
630
713
|
var AGENT_TOOL_NAMES = new Set(AGENT_TOOLS.map((t) => t.name));
|
|
@@ -634,7 +717,11 @@ var AGENT_TOOL_MODULE_MAP = {
|
|
|
634
717
|
"agent-save-documentation": "agent_reporting",
|
|
635
718
|
"agent-list-findings": "agent_reporting",
|
|
636
719
|
"agent-get-documentation": "agent_reporting",
|
|
637
|
-
"agent-validate-suggestions": "agent_reporting"
|
|
720
|
+
"agent-validate-suggestions": "agent_reporting",
|
|
721
|
+
"agent-check-lead-exists": "agent_reporting",
|
|
722
|
+
"agent-save-lead": "agent_reporting",
|
|
723
|
+
"agent-save-email-draft": "agent_reporting",
|
|
724
|
+
"agent-complete-target": "agent_reporting"
|
|
638
725
|
};
|
|
639
726
|
function clamp(val, min, max) {
|
|
640
727
|
return Math.max(min, Math.min(max, val));
|
|
@@ -670,7 +757,6 @@ async function handleAgentTool(name, args2, deps) {
|
|
|
670
757
|
const refrontProjectId = sanitizeString(args2.refront_project_id, 100);
|
|
671
758
|
const entries = Array.isArray(args2.entries) ? args2.entries : [];
|
|
672
759
|
if (!repoSlug) throw new Error("repo_slug is required");
|
|
673
|
-
if (!refrontProjectId) throw new Error("refront_project_id is required");
|
|
674
760
|
if (entries.length === 0) throw new Error("entries array must not be empty");
|
|
675
761
|
const wsId = deps.workspaceId;
|
|
676
762
|
const scanCommit = wsId ? `agent-scan-${wsId.slice(0, 8)}` : `agent-scan-${Date.now().toString(36)}`;
|
|
@@ -710,14 +796,13 @@ async function handleAgentTool(name, args2, deps) {
|
|
|
710
796
|
const category = args2.category;
|
|
711
797
|
const findings = Array.isArray(args2.findings) ? args2.findings : [];
|
|
712
798
|
if (!repoSlug) throw new Error("repo_slug is required");
|
|
713
|
-
if (!refrontProjectId) throw new Error("refront_project_id is required");
|
|
714
799
|
if (!category || !["scan_findings", "perf_audit"].includes(category)) {
|
|
715
800
|
throw new Error('category must be "scan_findings" or "perf_audit"');
|
|
716
801
|
}
|
|
717
802
|
if (findings.length === 0) throw new Error("findings array must not be empty");
|
|
718
803
|
const SIMILARITY_THRESHOLD = 0.55;
|
|
719
804
|
const MAX_OPEN_PER_TYPE = 30;
|
|
720
|
-
const { data: existingFindings } = await supabase2.from("doc_suggestion").select("id, type, description, file_path, severity, category, status").eq("repo_slug", repoSlug).in("status", ["open", "dismissed"]).limit(500);
|
|
805
|
+
const { data: existingFindings } = await supabase2.from("doc_suggestion").select("id, type, description, file_path, severity, category, status").eq("repo_slug", repoSlug).in("status", ["open", "dismissed", "resolved", "ticket_created"]).limit(500);
|
|
721
806
|
const existing = existingFindings ?? [];
|
|
722
807
|
const typeCountMap = /* @__PURE__ */ new Map();
|
|
723
808
|
for (const e of existing) {
|
|
@@ -738,6 +823,9 @@ async function handleAgentTool(name, args2, deps) {
|
|
|
738
823
|
if (!description) continue;
|
|
739
824
|
const isDuplicate = existing.some((e) => {
|
|
740
825
|
if (e.type !== findingType) return false;
|
|
826
|
+
if (filePath && e.file_path === filePath && ["dismissed", "resolved"].includes(e.status)) {
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
741
829
|
if (filePath && e.file_path === filePath) {
|
|
742
830
|
return textSimilarity(e.description ?? "", description) > 0.4;
|
|
743
831
|
}
|
|
@@ -812,7 +900,6 @@ ${items.map((i) => `\u2022 ${i}`).join("\n")}`;
|
|
|
812
900
|
const content = sanitizeString(args2.content, 1e5);
|
|
813
901
|
const reviewStatus = VALID_REVIEW_STATUSES.has(args2.review_status) ? args2.review_status : "pending";
|
|
814
902
|
if (!repoSlug) throw new Error("repo_slug is required");
|
|
815
|
-
if (!refrontProjectId) throw new Error("refront_project_id is required");
|
|
816
903
|
if (!path) throw new Error("path is required");
|
|
817
904
|
if (!title) throw new Error("title is required");
|
|
818
905
|
if (!content) throw new Error("content is required");
|
|
@@ -895,13 +982,16 @@ ${summary}`
|
|
|
895
982
|
return { content: [{ type: "text", text: `No documentation found for repo "${repoSlug}"` }] };
|
|
896
983
|
}
|
|
897
984
|
const output = docs.map((d) => {
|
|
898
|
-
const
|
|
985
|
+
const fullContent = String(d.content || "");
|
|
986
|
+
const isChangelog = d.scope === "changelog";
|
|
987
|
+
const maxPreview = isChangelog ? 5e4 : 500;
|
|
988
|
+
const preview = fullContent.slice(0, maxPreview);
|
|
899
989
|
return [
|
|
900
990
|
`## ${d.title} (${d.scope}/${d.path})`,
|
|
901
991
|
`Status: ${d.review_status} | By: ${d.generated_by || "unknown"}`,
|
|
902
992
|
`Updated: ${d.updated_at || d.created_at}`,
|
|
903
993
|
"",
|
|
904
|
-
|
|
994
|
+
preview + (fullContent.length > maxPreview ? "\n...(truncated)" : ""),
|
|
905
995
|
""
|
|
906
996
|
].join("\n");
|
|
907
997
|
}).join("\n---\n\n");
|
|
@@ -923,15 +1013,24 @@ ${output}` }]
|
|
|
923
1013
|
let adjusted = 0;
|
|
924
1014
|
let validated = 0;
|
|
925
1015
|
let errors = 0;
|
|
1016
|
+
let resolved = 0;
|
|
926
1017
|
for (const r of results) {
|
|
927
1018
|
const id = sanitizeString(r.suggestion_id, 100);
|
|
928
1019
|
const verdict = r.verdict;
|
|
929
1020
|
const reason = sanitizeString(r.reason, 2e3);
|
|
930
|
-
if (!id || !["valid", "invalid", "adjusted"].includes(verdict)) {
|
|
1021
|
+
if (!id || !["valid", "invalid", "adjusted", "resolved"].includes(verdict)) {
|
|
931
1022
|
errors++;
|
|
932
1023
|
continue;
|
|
933
1024
|
}
|
|
934
|
-
if (verdict === "
|
|
1025
|
+
if (verdict === "resolved") {
|
|
1026
|
+
const { error } = await supabase2.from("doc_suggestion").update({
|
|
1027
|
+
status: "resolved",
|
|
1028
|
+
validated_at: now,
|
|
1029
|
+
validated_by: validatedBy
|
|
1030
|
+
}).eq("id", id).eq("repo_slug", repoSlug);
|
|
1031
|
+
if (error) errors++;
|
|
1032
|
+
else resolved++;
|
|
1033
|
+
} else if (verdict === "invalid") {
|
|
935
1034
|
const { error } = await supabase2.from("doc_suggestion").update({
|
|
936
1035
|
status: "dismissed",
|
|
937
1036
|
dismissed_reason: reason || "Dismissed by validation agent",
|
|
@@ -964,6 +1063,7 @@ ${output}` }]
|
|
|
964
1063
|
}
|
|
965
1064
|
}
|
|
966
1065
|
const parts = [];
|
|
1066
|
+
if (resolved > 0) parts.push(`${resolved} resolved`);
|
|
967
1067
|
if (validated > 0) parts.push(`${validated} valid`);
|
|
968
1068
|
if (dismissed > 0) parts.push(`${dismissed} dismissed`);
|
|
969
1069
|
if (adjusted > 0) parts.push(`${adjusted} adjusted`);
|
|
@@ -976,6 +1076,151 @@ ${output}` }]
|
|
|
976
1076
|
};
|
|
977
1077
|
}
|
|
978
1078
|
// -----------------------------------------------------------------
|
|
1079
|
+
// Lead Generation tools
|
|
1080
|
+
// -----------------------------------------------------------------
|
|
1081
|
+
case "agent-check-lead-exists": {
|
|
1082
|
+
const websiteUrl = sanitizeString(args2.website_url, 500).replace(/\/+$/, "");
|
|
1083
|
+
const companyName = sanitizeString(args2.company_name, 500);
|
|
1084
|
+
if (!websiteUrl && !companyName) {
|
|
1085
|
+
throw new Error("At least one of website_url or company_name is required");
|
|
1086
|
+
}
|
|
1087
|
+
const checks = [];
|
|
1088
|
+
if (websiteUrl) {
|
|
1089
|
+
const { data: urlMatch } = await supabase2.from("lead").select("id, company_name, website_url").eq("website_url", websiteUrl).maybeSingle();
|
|
1090
|
+
if (urlMatch) {
|
|
1091
|
+
return {
|
|
1092
|
+
content: [{
|
|
1093
|
+
type: "text",
|
|
1094
|
+
text: `DUPLICATE: Lead already exists (URL match). ID: ${urlMatch.id}, Company: ${urlMatch.company_name}. SKIP this company.`
|
|
1095
|
+
}]
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
checks.push(`URL "${websiteUrl}" not found`);
|
|
1099
|
+
}
|
|
1100
|
+
if (companyName) {
|
|
1101
|
+
const normalized = normalizeCompanyName(companyName);
|
|
1102
|
+
if (normalized.length >= 3) {
|
|
1103
|
+
const { data: nameMatches } = await supabase2.from("lead").select("id, company_name, website_url").ilike("company_name", `%${normalized.split(" ")[0]}%`).limit(20);
|
|
1104
|
+
if (nameMatches) {
|
|
1105
|
+
for (const match of nameMatches) {
|
|
1106
|
+
const matchNorm = normalizeCompanyName(match.company_name);
|
|
1107
|
+
if (textSimilarity(normalized, matchNorm) > 0.6) {
|
|
1108
|
+
return {
|
|
1109
|
+
content: [{
|
|
1110
|
+
type: "text",
|
|
1111
|
+
text: `DUPLICATE: Similar company found. ID: ${match.id}, Name: "${match.company_name}" (${match.website_url}). SKIP this company.`
|
|
1112
|
+
}]
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
checks.push(`Name "${companyName}" has no similar matches`);
|
|
1119
|
+
}
|
|
1120
|
+
return {
|
|
1121
|
+
content: [{
|
|
1122
|
+
type: "text",
|
|
1123
|
+
text: `NOT FOUND: No duplicate detected. ${checks.join(". ")}. Safe to proceed.`
|
|
1124
|
+
}]
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
case "agent-save-lead": {
|
|
1128
|
+
const companyName = sanitizeString(args2.company_name, 500);
|
|
1129
|
+
const websiteUrl = sanitizeString(args2.website_url, 500).replace(/\/+$/, "");
|
|
1130
|
+
if (!companyName) throw new Error("company_name is required");
|
|
1131
|
+
if (!websiteUrl) throw new Error("website_url is required");
|
|
1132
|
+
const { data: existing } = await supabase2.from("lead").select("id, company_name").eq("website_url", websiteUrl).maybeSingle();
|
|
1133
|
+
if (existing) {
|
|
1134
|
+
return {
|
|
1135
|
+
content: [{
|
|
1136
|
+
type: "text",
|
|
1137
|
+
text: `Lead already exists (URL match). ID: ${existing.id}, Company: "${existing.company_name}". Skipped.`
|
|
1138
|
+
}]
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
const normalized = normalizeCompanyName(companyName);
|
|
1142
|
+
if (normalized.length >= 3) {
|
|
1143
|
+
const { data: nameMatches } = await supabase2.from("lead").select("id, company_name").ilike("company_name", `%${normalized.split(" ")[0]}%`).limit(20);
|
|
1144
|
+
if (nameMatches) {
|
|
1145
|
+
for (const match of nameMatches) {
|
|
1146
|
+
if (textSimilarity(normalized, normalizeCompanyName(match.company_name)) > 0.6) {
|
|
1147
|
+
return {
|
|
1148
|
+
content: [{
|
|
1149
|
+
type: "text",
|
|
1150
|
+
text: `Lead already exists (name match). ID: ${match.id}, Name: "${match.company_name}". Skipped.`
|
|
1151
|
+
}]
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
const fitScore = args2.fit_score ? clamp(Number(args2.fit_score), 1, 10) : null;
|
|
1158
|
+
const { data: inserted, error } = await supabase2.from("lead").insert({
|
|
1159
|
+
company_name: companyName,
|
|
1160
|
+
website_url: websiteUrl,
|
|
1161
|
+
industry: args2.industry ? sanitizeString(args2.industry, 200) : null,
|
|
1162
|
+
region: args2.region ? sanitizeString(args2.region, 200) : null,
|
|
1163
|
+
description: args2.description ? sanitizeString(args2.description, 2e3) : null,
|
|
1164
|
+
potential_fit: args2.potential_fit ? sanitizeString(args2.potential_fit, 2e3) : null,
|
|
1165
|
+
fit_score: fitScore,
|
|
1166
|
+
estimated_company_size: args2.estimated_company_size ? sanitizeString(args2.estimated_company_size, 50) : null,
|
|
1167
|
+
kvk_number: args2.kvk_number ? sanitizeString(args2.kvk_number, 20) : null,
|
|
1168
|
+
contact_name: args2.contact_name ? sanitizeString(args2.contact_name, 200) : null,
|
|
1169
|
+
contact_role: args2.contact_role ? sanitizeString(args2.contact_role, 200) : null,
|
|
1170
|
+
contact_email: args2.contact_email ? sanitizeString(args2.contact_email, 200) : null,
|
|
1171
|
+
contact_phone: args2.contact_phone ? sanitizeString(args2.contact_phone, 50) : null,
|
|
1172
|
+
contact_linkedin: args2.contact_linkedin ? sanitizeString(args2.contact_linkedin, 500) : null,
|
|
1173
|
+
general_email: args2.general_email ? sanitizeString(args2.general_email, 200) : null,
|
|
1174
|
+
general_phone: args2.general_phone ? sanitizeString(args2.general_phone, 50) : null,
|
|
1175
|
+
source_url: args2.source_url ? sanitizeString(args2.source_url, 500) : null,
|
|
1176
|
+
target_id: args2.target_id ? sanitizeString(args2.target_id, 100) : null,
|
|
1177
|
+
status: "new"
|
|
1178
|
+
}).select("id").single();
|
|
1179
|
+
if (error) throw new Error(`Failed to save lead: ${error.message}`);
|
|
1180
|
+
return {
|
|
1181
|
+
content: [{
|
|
1182
|
+
type: "text",
|
|
1183
|
+
text: `Lead saved: "${companyName}" (${websiteUrl}). ID: ${inserted.id}. Fit score: ${fitScore ?? "N/A"}.`
|
|
1184
|
+
}]
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
case "agent-save-email-draft": {
|
|
1188
|
+
const leadId = sanitizeString(args2.lead_id, 100);
|
|
1189
|
+
const subject = sanitizeString(args2.subject, 200);
|
|
1190
|
+
let body = sanitizeString(args2.body, 5e3);
|
|
1191
|
+
const tone = sanitizeString(args2.tone, 50) || "professional";
|
|
1192
|
+
if (!leadId) throw new Error("lead_id is required");
|
|
1193
|
+
if (!subject) throw new Error("subject is required");
|
|
1194
|
+
if (!body) throw new Error("body is required");
|
|
1195
|
+
body = body.replace(/[\u2013\u2014]/g, ",");
|
|
1196
|
+
const { data: inserted, error } = await supabase2.from("lead_email_draft").insert({ lead_id: leadId, subject, body, tone, status: "draft" }).select("id").single();
|
|
1197
|
+
if (error) throw new Error(`Failed to save email draft: ${error.message}`);
|
|
1198
|
+
await supabase2.from("lead").update({ status: "email_drafted", updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", leadId).eq("status", "new");
|
|
1199
|
+
return {
|
|
1200
|
+
content: [{
|
|
1201
|
+
type: "text",
|
|
1202
|
+
text: `Email draft saved for lead ${leadId}. Draft ID: ${inserted.id}. Subject: "${subject}".`
|
|
1203
|
+
}]
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
case "agent-complete-target": {
|
|
1207
|
+
const targetId = sanitizeString(args2.target_id, 100);
|
|
1208
|
+
const resultsCount = clamp(Number(args2.results_count) || 0, 0, 9999);
|
|
1209
|
+
if (!targetId) throw new Error("target_id is required");
|
|
1210
|
+
const { error } = await supabase2.from("lead_generation_target").update({
|
|
1211
|
+
status: "completed",
|
|
1212
|
+
results_count: resultsCount,
|
|
1213
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1214
|
+
}).eq("id", targetId);
|
|
1215
|
+
if (error) throw new Error(`Failed to complete target: ${error.message}`);
|
|
1216
|
+
return {
|
|
1217
|
+
content: [{
|
|
1218
|
+
type: "text",
|
|
1219
|
+
text: `Target ${targetId} marked completed. ${resultsCount} leads found.`
|
|
1220
|
+
}]
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
// -----------------------------------------------------------------
|
|
979
1224
|
default:
|
|
980
1225
|
return { content: [{ type: "text", text: `Unknown agent tool: ${name}` }] };
|
|
981
1226
|
}
|
|
@@ -2767,7 +3012,11 @@ async function main() {
|
|
|
2767
3012
|
"/api/save-documentation": "agent-save-documentation",
|
|
2768
3013
|
"/api/list-findings": "agent-list-findings",
|
|
2769
3014
|
"/api/get-documentation": "agent-get-documentation",
|
|
2770
|
-
"/api/validate-suggestions": "agent-validate-suggestions"
|
|
3015
|
+
"/api/validate-suggestions": "agent-validate-suggestions",
|
|
3016
|
+
"/api/check-lead-exists": "agent-check-lead-exists",
|
|
3017
|
+
"/api/save-lead": "agent-save-lead",
|
|
3018
|
+
"/api/save-email-draft": "agent-save-email-draft",
|
|
3019
|
+
"/api/complete-target": "agent-complete-target"
|
|
2771
3020
|
};
|
|
2772
3021
|
const httpServer = createServer(async (req, res) => {
|
|
2773
3022
|
const url = new URL(req.url ?? "/", `http://localhost:${httpPort}`);
|