@maestrogtm/maestro-gtm 0.10.16
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/ai-RNHSWSNV.js +158 -0
- package/dist/ai-RNHSWSNV.js.map +1 -0
- package/dist/app-PSZH2J56.js +54 -0
- package/dist/app-PSZH2J56.js.map +1 -0
- package/dist/batch-ZCHN54YJ.js +28 -0
- package/dist/batch-ZCHN54YJ.js.map +1 -0
- package/dist/campaign-XDXQA7KX.js +119 -0
- package/dist/campaign-XDXQA7KX.js.map +1 -0
- package/dist/chunk-365Q36GF.js +54 -0
- package/dist/chunk-365Q36GF.js.map +1 -0
- package/dist/chunk-4IV6QS4U.js +122 -0
- package/dist/chunk-4IV6QS4U.js.map +1 -0
- package/dist/chunk-6GLLK5KO.js +64 -0
- package/dist/chunk-6GLLK5KO.js.map +1 -0
- package/dist/chunk-6UNBW5SN.js +686 -0
- package/dist/chunk-6UNBW5SN.js.map +1 -0
- package/dist/chunk-A7JD6EYV.js +92 -0
- package/dist/chunk-A7JD6EYV.js.map +1 -0
- package/dist/chunk-ARNVJPFM.js +139 -0
- package/dist/chunk-ARNVJPFM.js.map +1 -0
- package/dist/chunk-AX6BOEF2.js +345 -0
- package/dist/chunk-AX6BOEF2.js.map +1 -0
- package/dist/chunk-C3T7QPSO.js +507 -0
- package/dist/chunk-C3T7QPSO.js.map +1 -0
- package/dist/chunk-FG43GILY.js +46 -0
- package/dist/chunk-FG43GILY.js.map +1 -0
- package/dist/chunk-FS6DCNCA.js +139 -0
- package/dist/chunk-FS6DCNCA.js.map +1 -0
- package/dist/chunk-I6GRD4X7.js +1144 -0
- package/dist/chunk-I6GRD4X7.js.map +1 -0
- package/dist/chunk-IP34URKR.js +621 -0
- package/dist/chunk-IP34URKR.js.map +1 -0
- package/dist/chunk-JFSKOY7Z.js +252 -0
- package/dist/chunk-JFSKOY7Z.js.map +1 -0
- package/dist/chunk-M25KLO7T.js +3272 -0
- package/dist/chunk-M25KLO7T.js.map +1 -0
- package/dist/chunk-M3G2WREL.js +57 -0
- package/dist/chunk-M3G2WREL.js.map +1 -0
- package/dist/chunk-MFFACSBE.js +4266 -0
- package/dist/chunk-MFFACSBE.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-QZH3XFOQ.js +2636 -0
- package/dist/chunk-QZH3XFOQ.js.map +1 -0
- package/dist/chunk-SPWDMOEU.js +1940 -0
- package/dist/chunk-SPWDMOEU.js.map +1 -0
- package/dist/chunk-TP3BZDVV.js +28 -0
- package/dist/chunk-TP3BZDVV.js.map +1 -0
- package/dist/chunk-UBJUBYSQ.js +18 -0
- package/dist/chunk-UBJUBYSQ.js.map +1 -0
- package/dist/chunk-VNKXGHWY.js +20 -0
- package/dist/chunk-VNKXGHWY.js.map +1 -0
- package/dist/chunk-WKLCPIFB.js +9862 -0
- package/dist/chunk-WKLCPIFB.js.map +1 -0
- package/dist/chunk-YV5XOXRQ.js +7 -0
- package/dist/chunk-YV5XOXRQ.js.map +1 -0
- package/dist/cli-Z3BNNJYQ.js +852 -0
- package/dist/cli-Z3BNNJYQ.js.map +1 -0
- package/dist/client-Y34LNEWN.js +8 -0
- package/dist/client-Y34LNEWN.js.map +1 -0
- package/dist/config.js +17 -0
- package/dist/config.js.map +1 -0
- package/dist/configure-XSENK4X5.js +64 -0
- package/dist/configure-XSENK4X5.js.map +1 -0
- package/dist/context.js +10 -0
- package/dist/context.js.map +1 -0
- package/dist/crm-QBNHVBYV.js +86 -0
- package/dist/crm-QBNHVBYV.js.map +1 -0
- package/dist/dfy-X3OXIYRA.js +356 -0
- package/dist/dfy-X3OXIYRA.js.map +1 -0
- package/dist/dist-LGCJKGBS.js +121 -0
- package/dist/dist-LGCJKGBS.js.map +1 -0
- package/dist/engagement-C4U7LPJH.js +463 -0
- package/dist/engagement-C4U7LPJH.js.map +1 -0
- package/dist/enrich-F5GPVZFE.js +226 -0
- package/dist/enrich-F5GPVZFE.js.map +1 -0
- package/dist/extract-DS5N6SSJ.js +155 -0
- package/dist/extract-DS5N6SSJ.js.map +1 -0
- package/dist/feedback-AIXKXNM5.js +51 -0
- package/dist/feedback-AIXKXNM5.js.map +1 -0
- package/dist/fetch-QJDSPI63.js +87 -0
- package/dist/fetch-QJDSPI63.js.map +1 -0
- package/dist/handlers.js +13 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/list-HL7NQQJX.js +236 -0
- package/dist/list-HL7NQQJX.js.map +1 -0
- package/dist/maestro-N7Q2JX22.js +903 -0
- package/dist/maestro-N7Q2JX22.js.map +1 -0
- package/dist/prospect-RUOT43H6.js +532 -0
- package/dist/prospect-RUOT43H6.js.map +1 -0
- package/dist/providers/factory.js +10 -0
- package/dist/providers/factory.js.map +1 -0
- package/dist/providers/registry.js +8 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/provision-FT5NWN77.js +394 -0
- package/dist/provision-FT5NWN77.js.map +1 -0
- package/dist/recipe-JU3SXMZF.js +137 -0
- package/dist/recipe-JU3SXMZF.js.map +1 -0
- package/dist/review-5SB6DYDZ.js +70 -0
- package/dist/review-5SB6DYDZ.js.map +1 -0
- package/dist/sdk-LVBHNQ6T.js +3852 -0
- package/dist/sdk-LVBHNQ6T.js.map +1 -0
- package/dist/server-REKYQZ2E.js +22 -0
- package/dist/server-REKYQZ2E.js.map +1 -0
- package/dist/status-V3EEFS7S.js +114 -0
- package/dist/status-V3EEFS7S.js.map +1 -0
- package/dist/tam-J6NDBP5W.js +682 -0
- package/dist/tam-J6NDBP5W.js.map +1 -0
- package/dist/tools.js +80 -0
- package/dist/tools.js.map +1 -0
- package/dist/validation.js +12 -0
- package/dist/validation.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/handlers/maestro.ts"],"sourcesContent":["/** Maestro handler. Knowledge graph CRUD and context assembly.\n * Purpose: Routes intel tool actions to @maestro/knowledge-graph services.\n * Constraint: Creates Supabase admin client from env vars for graph operations. */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport { logError } from '@maestro/logging';\n\nimport {\n personaRepo,\n productService,\n personaService,\n segmentService,\n proofPointService,\n competitorService,\n edgeService,\n outcomeService,\n changelogService,\n sourceDocumentService,\n assembleContext,\n clusterEntriesByTopic,\n buildProposal,\n loadExistingGraphSummary,\n loadExistingEntityNames,\n extractOntology,\n} from '@maestro/knowledge-graph';\nimport type {\n OwnerType,\n UpsertProductInput,\n UpsertPersonaInput,\n UpsertSegmentInput,\n AddProofPointInput,\n UpsertCompetitorInput,\n RecordOutcomeInput,\n ContextStrategy,\n BkgEdge,\n EntityCandidate,\n EntityMention,\n TopicCluster,\n ProposalEntityType,\n DocumentType,\n} from '@maestro/knowledge-graph';\nimport { HarvestClient } from '@maestro/integrations';\n\nimport { resolvePersona, applyPersonaBoost, parseTitle, parseCountry } from '@maestro/gtm';\n\nimport { createGtmError } from '../errors.js';\nimport { getSupabase, resolveOwner } from '../utils/supabase.js';\n\n// ─── Router ─────────────────────────────────────────────\n\nexport async function handleMaestro(name: string, args: Record<string, unknown>): Promise<unknown> {\n const supabase = getSupabase();\n const owner = resolveOwner(args);\n\n // Support both consolidated `intel` tool (action-based) and legacy `intel_*` tool names\n const action =\n name === 'intel'\n ? (args.action as string)\n : name.startsWith('intel_')\n ? name.slice(6) // 'intel_context' → 'context'\n : name;\n\n switch (action) {\n case 'context':\n return handleGetContext(supabase, owner, args);\n\n case 'product':\n return handleUpsertProduct(supabase, owner, args);\n\n case 'persona':\n return handleUpsertPersona(supabase, owner, args);\n\n case 'get_persona':\n return handleGetPersona(supabase, owner, args);\n\n case 'segment':\n return handleUpsertSegment(supabase, owner, args);\n\n case 'proof':\n return handleAddProofPoint(supabase, owner, args);\n\n case 'competitor':\n return handleAddCompetitor(supabase, owner, args);\n\n case 'outcome':\n return handleRecordOutcome(supabase, owner, args);\n\n case 'gaps':\n return handleGetGaps(supabase, owner, args);\n\n case 'propose':\n return handleProposeGraph(supabase, owner, args);\n\n case 'ingest':\n return handleIngestDocument(supabase, owner, args);\n\n case 'ingest_linkedin':\n return handleIngestLinkedIn(supabase, owner, args);\n\n case 'refresh_competitor':\n return handleRefreshCompetitor(supabase, owner, args);\n\n case 'changelog':\n return handleGetChangelog(supabase, owner, args);\n\n case 'score':\n return handleScoreContact(supabase, owner, args);\n\n default:\n throw new Error(`Unknown intel action: ${action}`);\n }\n}\n\n// ─── Context ────────────────────────────────────────────\n\nasync function handleGetContext(\n supabase: SupabaseClient,\n owner: { owner_id: string; owner_type: OwnerType },\n args: Record<string, unknown>\n) {\n const strategy = args.strategy as ContextStrategy;\n const query = args.query as string | undefined;\n const persona_id = args.persona_id as string | undefined;\n\n return assembleContext(supabase, {\n owner_id: owner.owner_id,\n owner_type: owner.owner_type,\n strategy,\n query,\n persona_id,\n });\n}\n\n// ─── Products ───────────────────────────────────────────\n\nasync function handleUpsertProduct(\n supabase: SupabaseClient,\n owner: { owner_id: string; owner_type: OwnerType },\n args: Record<string, unknown>\n) {\n const input: UpsertProductInput = {\n owner_id: owner.owner_id,\n owner_type: owner.owner_type,\n name: args.name as string,\n description: args.description as string | undefined,\n category: args.category as string | undefined,\n value_proposition: args.value_proposition as string | undefined,\n key_features: args.key_features as unknown[] | undefined,\n pricing_summary: args.pricing_summary as string | undefined,\n };\n\n return productService.upsertProduct(supabase, input);\n}\n\n// ─── Personas ───────────────────────────────────────────\n\nasync function handleUpsertPersona(\n supabase: SupabaseClient,\n owner: { owner_id: string; owner_type: OwnerType },\n args: Record<string, unknown>\n) {\n const input: UpsertPersonaInput = {\n owner_id: owner.owner_id,\n owner_type: owner.owner_type,\n name: args.name as string,\n title_patterns: args.title_patterns as string[] | undefined,\n industries: args.industries as string[] | undefined,\n company_size_min: args.company_size_min as number | undefined,\n company_size_max: args.company_size_max as number | undefined,\n pain_points: args.pain_points as string[] | undefined,\n goals: args.goals as string[] | undefined,\n objections: args.objections as string[] | undefined,\n buying_signals: args.buying_signals as string[] | undefined,\n geographic_focus: args.geographic_focus as string[] | undefined,\n budget_range: args.budget_range as string | undefined,\n is_primary: args.is_primary as boolean | undefined,\n };\n\n return personaService.upsertPersona(supabase, input);\n}\n\nasync function handleGetPersona(\n supabase: SupabaseClient,\n owner: { owner_id: string; owner_type: OwnerType },\n args: Record<string, unknown>\n) {\n const personaId = args.persona_id as string | undefined;\n\n if (personaId) {\n return personaService.getPersona(supabase, owner.owner_id, owner.owner_type, personaId);\n }\n\n return personaService.getPrimaryPersona(supabase, owner.owner_id, owner.owner_type);\n}\n\n// ─── Segments ───────────────────────────────────────────\n\nasync function handleUpsertSegment(\n supabase: SupabaseClient,\n owner: { owner_id: string; owner_type: OwnerType },\n args: Record<string, unknown>\n) {\n const input: UpsertSegmentInput = {\n owner_id: owner.owner_id,\n owner_type: owner.owner_type,\n name: args.name as string,\n description: args.description as string | undefined,\n qualification_criteria: args.qualification_criteria as unknown,\n market_size_estimate: args.market_size_estimate as string | undefined,\n };\n\n return segmentService.upsertSegment(supabase, input);\n}\n\n// ─── Proof Points ───────────────────────────────────────\n\nasync function handleAddProofPoint(\n supabase: SupabaseClient,\n owner: { owner_id: string; owner_type: OwnerType },\n args: Record<string, unknown>\n) {\n const input: AddProofPointInput = {\n owner_id: owner.owner_id,\n owner_type: owner.owner_type,\n type: args.type as AddProofPointInput['type'],\n title: args.title as string,\n content: args.content as string,\n metric_value: args.metric_value as string | undefined,\n source_type: args.source_type as string | undefined,\n source_id: args.source_id as string | undefined,\n };\n\n const result = await proofPointService.addProofPoint(supabase, input);\n\n // Auto-link to persona/product via edges if IDs provided\n // Edge creation must never block the proof point result (CLAUDE.md principle 7)\n if (result.success && result.data) {\n const proofPointId = result.data.id;\n\n if (args.persona_id) {\n try {\n await edgeService.createEdge(supabase, {\n owner_id: owner.owner_id,\n owner_type: owner.owner_type,\n source_type: 'proof_point',\n source_id: proofPointId,\n target_type: 'persona',\n target_id: args.persona_id as string,\n relationship: 'supports',\n });\n } catch (error) {\n logError(\n 'maestro.handleAddProofPoint.edgePersona',\n error instanceof Error ? error : new Error(String(error)),\n { proofPointId, personaId: args.persona_id }\n );\n }\n }\n\n if (args.product_id) {\n try {\n await edgeService.createEdge(supabase, {\n owner_id: owner.owner_id,\n owner_type: owner.owner_type,\n source_type: 'proof_point',\n source_id: proofPointId,\n target_type: 'product',\n target_id: args.product_id as string,\n relationship: 'demonstrates',\n });\n } catch (error) {\n logError(\n 'maestro.handleAddProofPoint.edgeProduct',\n error instanceof Error ? error : new Error(String(error)),\n { proofPointId, productId: args.product_id }\n );\n }\n }\n }\n\n return result;\n}\n\n// ─── Competitors ────────────────────────────────────────\n\nasync function handleAddCompetitor(\n supabase: SupabaseClient,\n owner: { owner_id: string; owner_type: OwnerType },\n args: Record<string, unknown>\n) {\n const input: UpsertCompetitorInput = {\n owner_id: owner.owner_id,\n owner_type: owner.owner_type,\n name: args.name as string,\n website: args.website as string | undefined,\n positioning: args.positioning as string | undefined,\n strengths: args.strengths as string[] | undefined,\n weaknesses: args.weaknesses as string[] | undefined,\n differentiation_notes: args.differentiation_notes as string | undefined,\n };\n\n return competitorService.upsertCompetitor(supabase, input);\n}\n\n// ─── Outcomes ───────────────────────────────────────────\n\nasync function handleRecordOutcome(\n supabase: SupabaseClient,\n owner: { owner_id: string; owner_type: OwnerType },\n args: Record<string, unknown>\n) {\n const input: RecordOutcomeInput = {\n owner_id: owner.owner_id,\n owner_type: owner.owner_type,\n outcome_type: args.outcome_type as RecordOutcomeInput['outcome_type'],\n entity_type: args.entity_type as string,\n entity_id: args.entity_id as string,\n detail: args.detail as string | undefined,\n metrics: args.metrics as Record<string, unknown> | undefined,\n occurred_at: (args.occurred_at as string) ?? new Date().toISOString(),\n };\n\n return outcomeService.recordOutcome(supabase, input);\n}\n\n// ─── Gap Analysis ───────────────────────────────────────\n\nasync function handleGetGaps(\n supabase: SupabaseClient,\n owner: { owner_id: string; owner_type: OwnerType },\n args: Record<string, unknown>\n) {\n const personaId = args.persona_id as string | undefined;\n\n // Assemble full context to analyze gaps\n const context = await assembleContext(supabase, {\n owner_id: owner.owner_id,\n owner_type: owner.owner_type,\n strategy: 'campaign_planning',\n persona_id: personaId,\n });\n\n const gaps: Array<{ entity_type: string; entity_name: string; gap: string; severity: string }> =\n [];\n\n // Check each persona for proof point coverage\n for (const persona of context.personas) {\n const linkedEdges = context.edges.filter(\n (e: BkgEdge) => e.target_type === 'persona' && e.target_id === persona.id\n );\n const linkedProofPoints = linkedEdges.filter((e: BkgEdge) => e.source_type === 'proof_point');\n\n if (linkedProofPoints.length < 2) {\n gaps.push({\n entity_type: 'persona',\n entity_name: persona.name,\n gap: `Only ${linkedProofPoints.length} proof points linked. Need at least 2.`,\n severity: linkedProofPoints.length === 0 ? 'high' : 'medium',\n });\n }\n\n if (persona.objections.length === 0) {\n gaps.push({\n entity_type: 'persona',\n entity_name: persona.name,\n gap: 'No objections documented. Record objections from calls and replies.',\n severity: 'medium',\n });\n }\n }\n\n // Check each product for proof point coverage\n for (const product of context.products) {\n const linkedEdges = context.edges.filter(\n (e: BkgEdge) => e.target_type === 'product' && e.target_id === product.id\n );\n const linkedProofPoints = linkedEdges.filter((e: BkgEdge) => e.source_type === 'proof_point');\n\n if (linkedProofPoints.length < 3) {\n gaps.push({\n entity_type: 'product',\n entity_name: product.name,\n gap: `Only ${linkedProofPoints.length} proof points. Need at least 3 for credibility.`,\n severity: linkedProofPoints.length === 0 ? 'high' : 'medium',\n });\n }\n }\n\n // Check for competitor intelligence\n if (context.competitors.length === 0) {\n gaps.push({\n entity_type: 'graph',\n entity_name: 'Competitors',\n gap: 'No competitors documented. Add competitive intelligence for differentiation.',\n severity: 'medium',\n });\n }\n\n return {\n persona_count: context.personas.length,\n product_count: context.products.length,\n proof_point_count: context.proof_points.length,\n competitor_count: context.competitors.length,\n segment_count: context.segments.length,\n gaps,\n gap_count: gaps.length,\n high_severity_count: gaps.filter((g) => g.severity === 'high').length,\n };\n}\n\n// ─── Graph Proposal ─────────────────────────────────────\n\nfunction getAnthropicKey(): string | null {\n return process.env.GTM_ANTHROPIC_API_KEY ?? process.env.ANTHROPIC_API_KEY ?? null;\n}\n\nconst VALID_PROPOSAL_TYPES: ProposalEntityType[] = [\n 'product',\n 'persona',\n 'segment',\n 'proof_point',\n 'competitor',\n];\n\nasync function handleProposeGraph(\n supabase: SupabaseClient,\n owner: { owner_id: string; owner_type: OwnerType },\n args: Record<string, unknown>\n) {\n const minEntries = (args.min_entries_per_topic as number) ?? 3;\n const maxClusters = (args.max_clusters as number) ?? 20;\n\n // 1. Load existing graph summary\n const summary = await loadExistingGraphSummary(supabase, owner.owner_id, owner.owner_type);\n\n // 2. Cluster knowledge entries by topic\n const clusters = await clusterEntriesByTopic(supabase, owner.owner_id, owner.owner_type, {\n minEntries,\n maxClusters,\n });\n\n if (clusters.length === 0) {\n return {\n proposals: [],\n clusters_analyzed: 0,\n candidates_extracted: 0,\n candidates_deduplicated: 0,\n existing_graph_summary: {\n products: summary.products.count,\n personas: summary.personas.count,\n segments: summary.segments.count,\n proof_points: summary.proof_points.count,\n competitors: summary.competitors.count,\n },\n message:\n `No topic clusters found with ${minEntries}+ entries. ` +\n 'Process more transcripts or lower min_entries_per_topic.',\n };\n }\n\n // 3. Extract entity candidates from each cluster via AI\n const apiKey = getAnthropicKey();\n const allCandidates: EntityCandidate[] = [];\n const existingNames = {\n products: summary.products.names,\n personas: summary.personas.names,\n segments: summary.segments.names,\n competitors: summary.competitors.names,\n };\n\n if (apiKey) {\n for (let i = 0; i < clusters.length; i++) {\n const candidates = await extractCandidatesFromCluster(clusters[i], existingNames, apiKey);\n allCandidates.push(...candidates);\n\n // Rate limiting between clusters\n if (i < clusters.length - 1) {\n await new Promise((r) => setTimeout(r, 500));\n }\n }\n } else {\n logError('maestro.handleProposeGraph', new Error('No Anthropic API key configured'), {\n hint: 'Set GTM_ANTHROPIC_API_KEY or ANTHROPIC_API_KEY',\n });\n }\n\n // 4. Deduplicate and build proposals\n return buildProposal({\n candidates: allCandidates,\n existing_entities: existingNames,\n proof_points_count: summary.proof_points.count,\n });\n}\n\n// ─── AI Entity Extraction ──────────────────────────────\n\nfunction buildExtractionPrompt(\n cluster: TopicCluster,\n existingNames: {\n products: string[];\n personas: string[];\n segments: string[];\n competitors: string[];\n }\n): string {\n const entriesText = cluster.entries\n .map(\n (e, i) =>\n `[${i + 1}] (${e.category}${e.knowledge_type ? '/' + e.knowledge_type : ''}) ${e.content}`\n )\n .join('\\n\\n');\n\n const existingSection = [\n existingNames.products.length > 0 ? `Products: ${existingNames.products.join(', ')}` : null,\n existingNames.personas.length > 0 ? `Personas: ${existingNames.personas.join(', ')}` : null,\n existingNames.segments.length > 0 ? `Segments: ${existingNames.segments.join(', ')}` : null,\n existingNames.competitors.length > 0\n ? `Competitors: ${existingNames.competitors.join(', ')}`\n : null,\n ]\n .filter(Boolean)\n .join('\\n');\n\n return `You are analyzing knowledge entries about the topic \"${cluster.topic_name}\" to identify business entities.\n\n${\n existingSection\n ? `EXISTING GRAPH ENTITIES (do NOT re-propose these):\\n${existingSection}\\n\\n`\n : 'No graph entities exist yet.\\n\\n'\n}KNOWLEDGE ENTRIES:\n${entriesText}\n\nIdentify any NEW entities mentioned in these entries:\n- Products/services being discussed or offered\n- Types of buyers/personas being described (job titles, industries, pain points)\n- Market segments being referenced\n- Proof points: results, metrics, case studies, testimonials cited\n- Competitors being mentioned\n\nFor each entity found, return:\n- entity_type: \"product\" | \"persona\" | \"segment\" | \"proof_point\" | \"competitor\"\n- name: concise entity name\n- description: 1-2 sentence description based on the entries\n- confidence: 0.0-1.0 (higher = more clearly and frequently referenced)\n- evidence: array of short quoted phrases from entries that support this entity\n- source_entries: array of entry numbers (e.g. [1, 3, 7]) that reference this entity\n\nReturn a JSON array. Empty array [] if no new entities found.\nExample: [{\"entity_type\":\"product\",\"name\":\"GTM Coaching\",\"description\":\"A coaching program for agency owners\",\"confidence\":0.85,\"evidence\":[\"our coaching program helps\"],\"source_entries\":[1,3]}]`;\n}\n\nasync function extractCandidatesFromCluster(\n cluster: TopicCluster,\n existingNames: {\n products: string[];\n personas: string[];\n segments: string[];\n competitors: string[];\n },\n apiKey: string\n): Promise<EntityCandidate[]> {\n try {\n const prompt = buildExtractionPrompt(cluster, existingNames);\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 60_000);\n\n let result: unknown;\n try {\n const response = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n 'anthropic-version': '2023-06-01',\n },\n body: JSON.stringify({\n model: 'claude-sonnet-4-5-20250514',\n max_tokens: 4096,\n messages: [{ role: 'user', content: prompt }],\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const body = await response.json().catch(() => ({}));\n logError('maestro.extractCandidates.api', new Error(`Anthropic ${response.status}`), {\n topic: cluster.topic_slug,\n body,\n });\n return [];\n }\n\n const data = (await response.json()) as { content: Array<{ text: string }> };\n const text = data.content[0]?.text ?? '';\n\n // Parse JSON from response (may be wrapped in markdown code blocks)\n const jsonMatch = text.match(/\\[[\\s\\S]*\\]/);\n if (!jsonMatch) return [];\n\n result = JSON.parse(jsonMatch[0]);\n } finally {\n clearTimeout(timeout);\n }\n\n if (!Array.isArray(result)) return [];\n\n // Validate and map to EntityCandidate[]\n const entryIds = cluster.entries.map((e) => e.id);\n\n return (result as Array<Record<string, unknown>>).filter(isValidRawCandidate).map((raw) => ({\n entity_type: raw.entity_type as ProposalEntityType,\n name: String(raw.name).trim(),\n description: String(raw.description ?? '').trim(),\n confidence: Math.max(0, Math.min(1, Number(raw.confidence) || 0.5)),\n evidence: Array.isArray(raw.evidence)\n ? raw.evidence.filter((e: unknown) => typeof e === 'string').map(String)\n : [],\n source_topic: cluster.topic_slug,\n source_entry_ids: Array.isArray(raw.source_entries)\n ? raw.source_entries\n .filter((n: unknown) => typeof n === 'number')\n .map((n: number) => entryIds[n - 1])\n .filter(Boolean)\n : [],\n }));\n } catch (error) {\n logError(\n 'maestro.extractCandidatesFromCluster',\n error instanceof Error ? error : new Error(String(error)),\n { topic: cluster.topic_slug }\n );\n return [];\n }\n}\n\nfunction isValidRawCandidate(raw: Record<string, unknown>): boolean {\n return (\n typeof raw.entity_type === 'string' &&\n VALID_PROPOSAL_TYPES.includes(raw.entity_type as ProposalEntityType) &&\n typeof raw.name === 'string' &&\n raw.name.trim().length > 0\n );\n}\n\n// ─── Entity Mention Classification ──────────────────────\n\nconst VALID_MENTION_ENTITY_TYPES = ['product', 'persona', 'competitor'];\n\nfunction buildClassificationPrompt(\n content: string,\n entityNames: { products: string[]; personas: string[]; competitors: string[] }\n): string {\n const sections: string[] = [];\n if (entityNames.products.length > 0)\n sections.push(`Products: ${entityNames.products.join(', ')}`);\n if (entityNames.personas.length > 0)\n sections.push(`Personas: ${entityNames.personas.join(', ')}`);\n if (entityNames.competitors.length > 0)\n sections.push(`Competitors: ${entityNames.competitors.join(', ')}`);\n\n const entityList = sections.join('\\n');\n\n return `You are an entity mention classifier. Given text content and a list of known business entities, identify which entities are referenced in the text.\n\nKNOWN ENTITIES:\n${entityList}\n\nTEXT CONTENT:\n${content.slice(0, 8000)}\n\nRULES:\n- ONLY match against the known entities listed above\n- Do NOT invent new entities\n- Match by meaning, not exact string — \"our coaching program\" matches a product named \"GTM Coaching\"\n- For personas, match when the text discusses pain points, goals, or characteristics of that persona type\n- For competitors, match when the text references or compares to that competitor\n- confidence: 0.0-1.0 based on how clearly the entity is referenced\n- evidence: quote the specific phrase(s) that indicate the mention\n\nRespond with ONLY a JSON array of matches (empty array if none):\n[{\"entity_type\": \"product\"|\"persona\"|\"competitor\", \"name\": \"exact name from the list above\", \"relationship\": \"mentions\"|\"addresses_pain_point\"|\"references_competitor\"|\"demonstrates_value\"|\"overcomes_objection\", \"confidence\": 0.8, \"evidence\": \"quoted phrase\"}]`;\n}\n\nfunction isValidMention(m: unknown): m is EntityMention {\n if (!m || typeof m !== 'object') return false;\n const obj = m as Record<string, unknown>;\n return (\n typeof obj.entity_type === 'string' &&\n VALID_MENTION_ENTITY_TYPES.includes(obj.entity_type) &&\n typeof obj.name === 'string' &&\n typeof obj.relationship === 'string' &&\n typeof obj.confidence === 'number' &&\n typeof obj.evidence === 'string' &&\n obj.confidence >= 0 &&\n obj.confidence <= 1\n );\n}\n\nasync function classifyEntityMentionsForGraph(\n content: string,\n entityNames: { products: string[]; personas: string[]; competitors: string[] }\n): Promise<EntityMention[]> {\n const totalEntities =\n entityNames.products.length + entityNames.personas.length + entityNames.competitors.length;\n if (totalEntities === 0) return [];\n\n const apiKey = getAnthropicKey();\n if (!apiKey) {\n logError('maestro.classifyEntityMentions', new Error('No Anthropic API key configured'), {\n hint: 'Set GTM_ANTHROPIC_API_KEY or ANTHROPIC_API_KEY',\n });\n return [];\n }\n\n try {\n const prompt = buildClassificationPrompt(content, entityNames);\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30_000);\n\n try {\n const response = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n 'anthropic-version': '2023-06-01',\n },\n body: JSON.stringify({\n model: 'claude-haiku-4-5-20251001',\n max_tokens: 1024,\n messages: [{ role: 'user', content: prompt }],\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const body = await response.json().catch(() => ({}));\n logError('maestro.classifyEntityMentions.api', new Error(`Anthropic ${response.status}`), {\n body,\n });\n return [];\n }\n\n const data = (await response.json()) as { content: Array<{ text: string }> };\n const text = data.content[0]?.text ?? '';\n\n const jsonMatch = text.match(/\\[[\\s\\S]*\\]/);\n if (!jsonMatch) return [];\n\n const parsed = JSON.parse(jsonMatch[0]);\n if (!Array.isArray(parsed)) return [];\n\n return (parsed as unknown[]).filter(isValidMention);\n } finally {\n clearTimeout(timeout);\n }\n } catch (err) {\n logError(\n 'maestro.classifyEntityMentionsForGraph',\n err instanceof Error ? err : new Error(String(err))\n );\n return [];\n }\n}\n\n// ─── Document Ingestion ─────────────────────────────────\n\nasync function handleIngestDocument(\n supabase: SupabaseClient,\n owner: { owner_id: string; owner_type: OwnerType },\n args: Record<string, unknown>\n) {\n const filename = args.filename as string;\n const documentType = ((args.document_type as string) ?? 'other') as DocumentType;\n const content = args.content as string | undefined;\n\n // 1. Create source document record\n const docResult = await sourceDocumentService.createDocument(supabase, {\n owner_id: owner.owner_id,\n owner_type: owner.owner_type,\n filename,\n document_type: documentType,\n });\n\n if (!docResult.success) {\n throw createGtmError(\n 'PROVIDER_REQUEST_FAILED',\n `Failed to record document: ${docResult.error}`\n );\n }\n\n const doc = docResult.data;\n\n // 2. If content provided, classify and extract ontology\n if (!content) {\n return {\n success: true,\n document: doc,\n edges_created: 0,\n entities_matched: 0,\n message:\n 'Document recorded. No content provided — use content param for automatic extraction.',\n };\n }\n\n await sourceDocumentService.markProcessing(supabase, doc.id);\n\n try {\n const entityNames = await loadExistingEntityNames(supabase, owner.owner_id, owner.owner_type);\n const mentions = await classifyEntityMentionsForGraph(content, entityNames);\n\n let edgesCreated = 0;\n if (mentions.length > 0) {\n const result = await extractOntology(supabase, {\n owner_id: owner.owner_id,\n owner_type: owner.owner_type,\n knowledge_entry_id: doc.id,\n source_type: 'source_document',\n source_id: doc.id,\n mentions,\n });\n if (result.success) {\n edgesCreated = result.data.edges_created;\n }\n }\n\n await sourceDocumentService.markCompleted(supabase, doc.id, edgesCreated);\n\n return {\n success: true,\n document: doc,\n edges_created: edgesCreated,\n entities_matched: mentions.length,\n message: `Document processed. ${mentions.length} entity mentions found, ${edgesCreated} edges created.`,\n };\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n await sourceDocumentService.markFailed(supabase, doc.id, errorMsg);\n logError('maestro.handleIngestDocument', err instanceof Error ? err : new Error(String(err)), {\n filename,\n docId: doc.id,\n });\n throw createGtmError('PROVIDER_REQUEST_FAILED', `Document processing failed: ${errorMsg}`);\n }\n}\n\n// ─── LinkedIn Ingestion ─────────────────────────────────\n\nfunction getHarvestApiKey(): string | null {\n return process.env.GTM_HARVEST_API_KEY ?? null;\n}\n\nasync function handleIngestLinkedIn(\n supabase: SupabaseClient,\n owner: { owner_id: string; owner_type: OwnerType },\n args: Record<string, unknown>\n) {\n const linkedinUrl = args.linkedin_url as string;\n const includePosts = (args.include_posts as boolean) ?? false;\n\n const harvestKey = getHarvestApiKey();\n if (!harvestKey) {\n throw createGtmError(\n 'PROVIDER_NOT_CONFIGURED',\n 'HarvestAPI key not configured. Set GTM_HARVEST_API_KEY.'\n );\n }\n\n const harvest = new HarvestClient({ apiKey: harvestKey });\n\n // 1. Fetch profile\n const profile = await harvest.getProfile(linkedinUrl);\n\n // 2. Build classifiable text from profile data\n const textParts: string[] = [];\n if (profile.headline) textParts.push(profile.headline);\n if (profile.about) textParts.push(profile.about);\n if (profile.experience && Array.isArray(profile.experience)) {\n for (const exp of profile.experience) {\n const parts = [exp.position, exp.companyName, exp.description].filter(Boolean);\n if (parts.length > 0) textParts.push(parts.join(' at '));\n }\n }\n\n // 3. Optionally fetch posts\n if (includePosts) {\n try {\n const postsResponse = await harvest.getProfilePosts({ profile: linkedinUrl });\n if (postsResponse.elements && Array.isArray(postsResponse.elements)) {\n for (const post of postsResponse.elements.slice(0, 10)) {\n if (post.content) textParts.push(post.content);\n }\n }\n } catch (err) {\n logError(\n 'maestro.handleIngestLinkedIn.posts',\n err instanceof Error ? err : new Error(String(err)),\n { linkedinUrl }\n );\n // Posts are optional — continue without them\n }\n }\n\n const fullText = textParts.join('\\n\\n');\n if (!fullText.trim()) {\n return {\n success: true,\n profile_name: `${profile.firstName ?? ''} ${profile.lastName ?? ''}`.trim(),\n edges_created: 0,\n entities_matched: 0,\n message: 'Profile scraped but no classifiable text found.',\n };\n }\n\n // 4. Create source document\n const docResult = await sourceDocumentService.createDocument(\n supabase,\n {\n owner_id: owner.owner_id,\n owner_type: owner.owner_type,\n filename: linkedinUrl,\n document_type: 'other',\n },\n 'linkedin_ingester'\n );\n\n if (!docResult.success) {\n throw createGtmError(\n 'PROVIDER_REQUEST_FAILED',\n `Failed to record LinkedIn source: ${docResult.error}`\n );\n }\n\n const doc = docResult.data;\n await sourceDocumentService.markProcessing(supabase, doc.id);\n\n // 5. Classify and extract\n try {\n const entityNames = await loadExistingEntityNames(supabase, owner.owner_id, owner.owner_type);\n const mentions = await classifyEntityMentionsForGraph(fullText, entityNames);\n\n let edgesCreated = 0;\n if (mentions.length > 0) {\n const result = await extractOntology(supabase, {\n owner_id: owner.owner_id,\n owner_type: owner.owner_type,\n knowledge_entry_id: doc.id,\n source_type: 'source_document',\n source_id: doc.id,\n mentions,\n });\n if (result.success) {\n edgesCreated = result.data.edges_created;\n }\n }\n\n await sourceDocumentService.markCompleted(supabase, doc.id, edgesCreated);\n\n return {\n success: true,\n profile_name: `${profile.firstName ?? ''} ${profile.lastName ?? ''}`.trim(),\n edges_created: edgesCreated,\n entities_matched: mentions.length,\n source_document_id: doc.id,\n message:\n `LinkedIn profile processed. ${mentions.length} entity mentions, ${edgesCreated} edges.` +\n (includePosts ? ' Posts included.' : ''),\n };\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n await sourceDocumentService.markFailed(supabase, doc.id, errorMsg);\n logError('maestro.handleIngestLinkedIn', err instanceof Error ? err : new Error(String(err)), {\n linkedinUrl,\n docId: doc.id,\n });\n throw createGtmError('PROVIDER_REQUEST_FAILED', `LinkedIn ingestion failed: ${errorMsg}`);\n }\n}\n\n// ─── Competitor Refresh ─────────────────────────────────\n\nfunction getExaApiKey(): string | null {\n return process.env.GTM_EXA_API_KEY ?? null;\n}\n\nasync function handleRefreshCompetitor(\n supabase: SupabaseClient,\n owner: { owner_id: string; owner_type: OwnerType },\n args: Record<string, unknown>\n) {\n const competitorName = args.competitor_name as string | undefined;\n const customQuery = args.search_query as string | undefined;\n\n if (!competitorName && !customQuery) {\n throw createGtmError('QUERY_INVALID', 'Provide competitor_name and/or search_query.');\n }\n\n const exaKey = getExaApiKey();\n const anthropicKey = getAnthropicKey();\n\n if (!exaKey) {\n throw createGtmError(\n 'PROVIDER_NOT_CONFIGURED',\n 'Exa API key not configured. Set GTM_EXA_API_KEY.'\n );\n }\n if (!anthropicKey) {\n throw createGtmError(\n 'PROVIDER_NOT_CONFIGURED',\n 'Anthropic API key not configured. Set GTM_ANTHROPIC_API_KEY.'\n );\n }\n\n // 1. Search Exa\n const searchQuery =\n customQuery ?? `${competitorName} company positioning pricing features services`;\n\n const exaResponse = await fetch('https://api.exa.ai/search', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': exaKey,\n },\n body: JSON.stringify({\n query: searchQuery,\n type: 'neural',\n numResults: 5,\n contents: {\n text: { maxCharacters: 2000 },\n summary: {\n query: `What is ${competitorName ?? 'this company'}'s positioning, strengths, and weaknesses?`,\n },\n },\n }),\n signal: AbortSignal.timeout(15_000),\n });\n\n if (!exaResponse.ok) {\n const errText = await exaResponse.text().catch(() => '');\n throw createGtmError(\n 'PROVIDER_REQUEST_FAILED',\n `Exa search failed (${exaResponse.status}): ${errText}`\n );\n }\n\n const exaData = (await exaResponse.json()) as {\n results: Array<{ url: string; title?: string; text?: string; summary?: string }>;\n };\n const results = exaData.results ?? [];\n\n if (results.length === 0) {\n return {\n success: true,\n competitor_name: competitorName,\n fields_updated: 0,\n sources_checked: 0,\n message: 'No search results found.',\n };\n }\n\n // 3. AI extraction of competitor intel from search results\n const webContent = results\n .map((r, i) => `[Source ${i + 1}] ${r.title ?? r.url}\\n${r.summary ?? ''}\\n${r.text ?? ''}`)\n .join('\\n\\n---\\n\\n');\n\n const extractionPrompt = `Analyze the following web search results about \"${competitorName ?? 'the company'}\".\nExtract structured competitive intelligence.\n\nWEB RESULTS:\n${webContent.slice(0, 6000)}\n\nReturn ONLY a JSON object with these fields (empty string/array if not found):\n{\n \"positioning\": \"How they position themselves in the market (1-2 sentences)\",\n \"strengths\": [\"strength1\", \"strength2\"],\n \"weaknesses\": [\"weakness1\", \"weakness2\"],\n \"differentiation_notes\": \"Key differentiators or unique selling points\",\n \"website\": \"their main website domain if found\"\n}`;\n\n const aiResponse = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': anthropicKey,\n 'anthropic-version': '2023-06-01',\n },\n body: JSON.stringify({\n model: 'claude-haiku-4-5-20251001',\n max_tokens: 1024,\n messages: [{ role: 'user', content: extractionPrompt }],\n }),\n signal: AbortSignal.timeout(30_000),\n });\n\n if (!aiResponse.ok) {\n throw createGtmError('PROVIDER_REQUEST_FAILED', `AI extraction failed: ${aiResponse.status}`);\n }\n\n const aiData = (await aiResponse.json()) as { content: Array<{ text: string }> };\n const aiText = aiData.content[0]?.text ?? '';\n\n const jsonMatch = aiText.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) {\n return {\n success: true,\n competitor_name: competitorName,\n fields_updated: 0,\n sources_checked: results.length,\n message: 'Could not extract structured intel from search results.',\n };\n }\n\n let intel: Record<string, unknown>;\n try {\n intel = JSON.parse(jsonMatch[0]);\n } catch (parseErr) {\n logError(\n 'maestro.refreshCompetitor.parse',\n parseErr instanceof Error ? parseErr : new Error(String(parseErr)),\n { competitor_name: competitorName, raw: jsonMatch[0]?.slice(0, 200) }\n );\n return {\n success: true,\n competitor_name: competitorName,\n fields_updated: 0,\n sources_checked: results.length,\n message: 'Failed to parse AI extraction result.',\n };\n }\n\n // 4. Upsert competitor\n const upsertInput: UpsertCompetitorInput = {\n owner_id: owner.owner_id,\n owner_type: owner.owner_type,\n name: competitorName ?? (typeof intel.website === 'string' ? intel.website : 'Unknown'),\n };\n\n let fieldsUpdated = 0;\n if (typeof intel.positioning === 'string' && intel.positioning) {\n upsertInput.positioning = intel.positioning;\n fieldsUpdated++;\n }\n if (Array.isArray(intel.strengths) && intel.strengths.length > 0) {\n upsertInput.strengths = intel.strengths.filter((s: unknown) => typeof s === 'string');\n fieldsUpdated++;\n }\n if (Array.isArray(intel.weaknesses) && intel.weaknesses.length > 0) {\n upsertInput.weaknesses = intel.weaknesses.filter((s: unknown) => typeof s === 'string');\n fieldsUpdated++;\n }\n if (typeof intel.differentiation_notes === 'string' && intel.differentiation_notes) {\n upsertInput.differentiation_notes = intel.differentiation_notes;\n fieldsUpdated++;\n }\n if (typeof intel.website === 'string' && intel.website) {\n upsertInput.website = intel.website;\n fieldsUpdated++;\n }\n\n await competitorService.upsertCompetitor(supabase, upsertInput, 'web_scraper');\n\n return {\n success: true,\n competitor_name: upsertInput.name,\n fields_updated: fieldsUpdated,\n sources_checked: results.length,\n extracted_intel: intel,\n message: `Competitor \"${upsertInput.name}\" updated with ${fieldsUpdated} fields from ${results.length} web sources.`,\n };\n}\n\n// ─── Changelog ──────────────────────────────────────────\n\nasync function handleGetChangelog(\n supabase: SupabaseClient,\n owner: { owner_id: string; owner_type: OwnerType },\n args: Record<string, unknown>\n) {\n const entityId = args.entity_id as string | undefined;\n const entityType = args.entity_type as string | undefined;\n\n if (entityId && entityType) {\n return changelogService.getEntityChangelog(\n supabase,\n owner.owner_id,\n owner.owner_type,\n entityType,\n entityId\n );\n }\n\n return changelogService.getHistory(supabase, owner.owner_id, owner.owner_type, {\n limit: (args.limit as number) ?? 50,\n entityType,\n source: args.source as string | undefined,\n });\n}\n\n// ─── Score Contact ─────────────────────────────────────\n\nasync function handleScoreContact(\n supabase: SupabaseClient,\n owner: { owner_id: string; owner_type: OwnerType },\n args: Record<string, unknown>\n) {\n const headline = args.headline as string | undefined;\n const location = args.location as string | undefined;\n\n const contactSnapshot = {\n parsed_title: headline ? parseTitle(headline) : null,\n headline: headline ?? null,\n location: location ?? null,\n parsed_country: location ? parseCountry(location) : null,\n company_name: (args.company_name as string) ?? null,\n company_size: (args.company_size as string) ?? null,\n industry: (args.industry as string) ?? null,\n };\n\n const personas = await personaRepo.listPersonas(supabase, owner.owner_id, owner.owner_type);\n\n const personaMatch = resolvePersona(contactSnapshot, personas);\n\n const baseScore = (args.base_score as number) ?? 0;\n const boostResult = applyPersonaBoost(baseScore, personaMatch);\n\n return {\n persona_match: personaMatch,\n boost: boostResult,\n personas_checked: personas.length,\n contact_snapshot: contactSnapshot,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runWaterfall
|
|
3
|
+
} from "./chunk-AX6BOEF2.js";
|
|
4
|
+
import {
|
|
5
|
+
shouldWriteToFile,
|
|
6
|
+
writeResultsCsv,
|
|
7
|
+
writeResultsFile
|
|
8
|
+
} from "./chunk-A7JD6EYV.js";
|
|
9
|
+
import {
|
|
10
|
+
createReviewTable
|
|
11
|
+
} from "./chunk-4IV6QS4U.js";
|
|
12
|
+
import {
|
|
13
|
+
CONTROL_PARAMS,
|
|
14
|
+
MANIFESTS,
|
|
15
|
+
getManifest,
|
|
16
|
+
getToolManifest
|
|
17
|
+
} from "./chunk-FS6DCNCA.js";
|
|
18
|
+
import {
|
|
19
|
+
createGtmError
|
|
20
|
+
} from "./chunk-UBJUBYSQ.js";
|
|
21
|
+
import {
|
|
22
|
+
logError,
|
|
23
|
+
logWarn
|
|
24
|
+
} from "./chunk-6GLLK5KO.js";
|
|
25
|
+
import "./chunk-PZ5AY32C.js";
|
|
26
|
+
|
|
27
|
+
// src/providers/types.ts
|
|
28
|
+
var SLIM_COMPANY_FIELDS = [
|
|
29
|
+
"domain",
|
|
30
|
+
"name",
|
|
31
|
+
"description",
|
|
32
|
+
"industry",
|
|
33
|
+
"employees",
|
|
34
|
+
"country",
|
|
35
|
+
"linkedin_url",
|
|
36
|
+
"tech_stack",
|
|
37
|
+
"funding",
|
|
38
|
+
"revenue_estimate",
|
|
39
|
+
"score"
|
|
40
|
+
];
|
|
41
|
+
var SLIM_PERSON_FIELDS = [
|
|
42
|
+
"first_name",
|
|
43
|
+
"last_name",
|
|
44
|
+
"job_title",
|
|
45
|
+
"email",
|
|
46
|
+
"phone",
|
|
47
|
+
"company",
|
|
48
|
+
"company_domain",
|
|
49
|
+
"linkedin_url",
|
|
50
|
+
"seniority",
|
|
51
|
+
"department",
|
|
52
|
+
"location"
|
|
53
|
+
];
|
|
54
|
+
function applySlimFields(obj, fields) {
|
|
55
|
+
const result = {};
|
|
56
|
+
for (const field of fields) {
|
|
57
|
+
if (field in obj) {
|
|
58
|
+
result[field] = obj[field];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/providers/manifest-validation.ts
|
|
65
|
+
var controlSet = new Set(CONTROL_PARAMS);
|
|
66
|
+
function validateParams(provider, tool, args) {
|
|
67
|
+
const manifest = getToolManifest(provider, tool);
|
|
68
|
+
if (!manifest) return [];
|
|
69
|
+
const supported = new Set(manifest.supported_params);
|
|
70
|
+
const warnings = [];
|
|
71
|
+
for (const [key, value] of Object.entries(args)) {
|
|
72
|
+
if (value === void 0 || value === null) continue;
|
|
73
|
+
if (controlSet.has(key)) continue;
|
|
74
|
+
if (!supported.has(key)) {
|
|
75
|
+
const alternatives = Object.entries(MANIFESTS).filter(([_, m]) => m[tool]?.supported_params.includes(key)).map(([name]) => name);
|
|
76
|
+
const suggestion = alternatives.length > 0 ? ` Use provider: '${alternatives[0]}' for ${key} filtering.` : "";
|
|
77
|
+
warnings.push(`${key} is not supported by ${provider}.${suggestion}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return warnings;
|
|
81
|
+
}
|
|
82
|
+
function clampLimit(provider, tool, limit) {
|
|
83
|
+
const manifest = getToolManifest(provider, tool);
|
|
84
|
+
if (!manifest) return limit ?? 50;
|
|
85
|
+
if (limit === void 0) return manifest.default_limit;
|
|
86
|
+
return Math.min(limit, manifest.max_limit);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/utils/title-expansion.ts
|
|
90
|
+
var TITLE_CACHE = /* @__PURE__ */ new Map();
|
|
91
|
+
var SKIP_EXPANSION = /* @__PURE__ */ new Set([
|
|
92
|
+
"ceo",
|
|
93
|
+
"cto",
|
|
94
|
+
"cfo",
|
|
95
|
+
"coo",
|
|
96
|
+
"cmo",
|
|
97
|
+
"cro",
|
|
98
|
+
"ciso",
|
|
99
|
+
"cpo",
|
|
100
|
+
"vp sales",
|
|
101
|
+
"vp marketing",
|
|
102
|
+
"vp engineering",
|
|
103
|
+
"vp product",
|
|
104
|
+
"head of sales",
|
|
105
|
+
"head of marketing",
|
|
106
|
+
"head of engineering",
|
|
107
|
+
"head of product",
|
|
108
|
+
"director of engineering",
|
|
109
|
+
"director of sales",
|
|
110
|
+
"director of marketing",
|
|
111
|
+
"software engineer",
|
|
112
|
+
"product manager",
|
|
113
|
+
"account executive",
|
|
114
|
+
"sales development representative",
|
|
115
|
+
"sdr",
|
|
116
|
+
"bdr",
|
|
117
|
+
"solutions engineer",
|
|
118
|
+
"customer success manager"
|
|
119
|
+
]);
|
|
120
|
+
var EXPANSION_PROMPT = `You are a GTM title expert. Given a job title, return 3-6 equivalent or closely related titles that someone with this role might also be called. These will be used as search filters in prospect databases.
|
|
121
|
+
|
|
122
|
+
Rules:
|
|
123
|
+
- Include the original title
|
|
124
|
+
- Include common synonyms and abbreviations
|
|
125
|
+
- Include both formal and informal variants
|
|
126
|
+
- Only return titles for the SAME level/function \u2014 don't expand "VP Sales" to "SDR"
|
|
127
|
+
- Return as a JSON array of strings, nothing else
|
|
128
|
+
|
|
129
|
+
Title: "TITLE_PLACEHOLDER"`;
|
|
130
|
+
async function expandTitle(title, apiKey) {
|
|
131
|
+
const normalized = title.toLowerCase().trim();
|
|
132
|
+
const cached = TITLE_CACHE.get(normalized);
|
|
133
|
+
if (cached) return cached;
|
|
134
|
+
if (SKIP_EXPANSION.has(normalized)) {
|
|
135
|
+
TITLE_CACHE.set(normalized, [title]);
|
|
136
|
+
return [title];
|
|
137
|
+
}
|
|
138
|
+
const key = apiKey || process.env.GTM_ANTHROPIC_API_KEY;
|
|
139
|
+
if (!key) {
|
|
140
|
+
TITLE_CACHE.set(normalized, [title]);
|
|
141
|
+
return [title];
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
145
|
+
method: "POST",
|
|
146
|
+
headers: {
|
|
147
|
+
"Content-Type": "application/json",
|
|
148
|
+
"x-api-key": key,
|
|
149
|
+
"anthropic-version": "2023-06-01"
|
|
150
|
+
},
|
|
151
|
+
body: JSON.stringify({
|
|
152
|
+
model: "claude-haiku-4-5-20251001",
|
|
153
|
+
max_tokens: 200,
|
|
154
|
+
messages: [{ role: "user", content: EXPANSION_PROMPT.replace("TITLE_PLACEHOLDER", title) }]
|
|
155
|
+
})
|
|
156
|
+
});
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
TITLE_CACHE.set(normalized, [title]);
|
|
159
|
+
return [title];
|
|
160
|
+
}
|
|
161
|
+
const data = await response.json();
|
|
162
|
+
const raw = data.content?.[0]?.text || "";
|
|
163
|
+
const text = raw.replace(/```json\n?|\n?```/g, "").trim();
|
|
164
|
+
const variants = JSON.parse(text);
|
|
165
|
+
if (Array.isArray(variants) && variants.length > 0) {
|
|
166
|
+
TITLE_CACHE.set(normalized, variants);
|
|
167
|
+
return variants;
|
|
168
|
+
}
|
|
169
|
+
} catch (err) {
|
|
170
|
+
logWarn("title-expansion", "Failed to expand title, using original", {
|
|
171
|
+
title,
|
|
172
|
+
error: err instanceof Error ? err.message : String(err)
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
TITLE_CACHE.set(normalized, [title]);
|
|
176
|
+
return [title];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/handlers/prospect.ts
|
|
180
|
+
function deduplicatePeople(people) {
|
|
181
|
+
const seen = /* @__PURE__ */ new Set();
|
|
182
|
+
return people.filter((p) => {
|
|
183
|
+
const key = p.linkedin_url ?? p.email ?? `${p.first_name}_${p.last_name}_${p.company}`;
|
|
184
|
+
if (seen.has(key)) return false;
|
|
185
|
+
seen.add(key);
|
|
186
|
+
return true;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
async function handleProspect(name, args, ctx) {
|
|
190
|
+
if (name === "find_companies") {
|
|
191
|
+
return findCompanies(args, ctx);
|
|
192
|
+
}
|
|
193
|
+
return findPeople(args, ctx);
|
|
194
|
+
}
|
|
195
|
+
async function findCompanies(args, ctx) {
|
|
196
|
+
const providerName = args.provider || void 0;
|
|
197
|
+
const providers = providerName ? [ctx.registry.get(providerName)].filter(Boolean) : ctx.registry.getForCapability("find_companies").filter((p) => getManifest(p.name)?.auto_selectable !== false);
|
|
198
|
+
if (providers.length === 0) {
|
|
199
|
+
const registered = ctx.registry.list().map((p) => p.name);
|
|
200
|
+
if (providerName) {
|
|
201
|
+
const available = registered.length > 0 ? registered.join(", ") : "none";
|
|
202
|
+
throw createGtmError(
|
|
203
|
+
"PROVIDER_NOT_CONFIGURED",
|
|
204
|
+
`Provider "${providerName}" is not registered or does not support company discovery. Available providers: ${available}.`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
throw createGtmError(
|
|
208
|
+
"PROVIDER_NOT_CONFIGURED",
|
|
209
|
+
"No providers configured for company discovery. Add a provider with find_companies capability."
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
const provider = providers[0];
|
|
213
|
+
const finder = provider;
|
|
214
|
+
const domain = args.domain;
|
|
215
|
+
const isDirectLookup = domain && !args.domains && !args.icp_text && !args.category && !args.tech_stack && !args.country && finder.getCompany;
|
|
216
|
+
if (isDirectLookup) {
|
|
217
|
+
const company = await finder.getCompany(domain);
|
|
218
|
+
if (!company) {
|
|
219
|
+
throw new Error(`Company not found: ${domain}`);
|
|
220
|
+
}
|
|
221
|
+
return { provider: provider.name, count: 1, has_more: false, offset: 0, companies: [company] };
|
|
222
|
+
}
|
|
223
|
+
const effectiveLimit = clampLimit(
|
|
224
|
+
provider.name,
|
|
225
|
+
"find_companies",
|
|
226
|
+
args.limit
|
|
227
|
+
);
|
|
228
|
+
const warnings = providerName ? validateParams(providerName, "find_companies", args) : void 0;
|
|
229
|
+
const query = {
|
|
230
|
+
domain,
|
|
231
|
+
icp_text: args.icp_text,
|
|
232
|
+
limit: effectiveLimit,
|
|
233
|
+
country: args.country,
|
|
234
|
+
min_employees: args.min_employees,
|
|
235
|
+
max_employees: args.max_employees,
|
|
236
|
+
domains: args.domains,
|
|
237
|
+
tech_stack: args.tech_stack,
|
|
238
|
+
category: args.category,
|
|
239
|
+
negate_domains: args.negate_domains,
|
|
240
|
+
min_digital_footprint: args.min_digital_footprint,
|
|
241
|
+
max_digital_footprint: args.max_digital_footprint,
|
|
242
|
+
offset: args.offset,
|
|
243
|
+
search: args.search,
|
|
244
|
+
state: args.state,
|
|
245
|
+
platform: args.platform,
|
|
246
|
+
min_revenue: args.min_revenue,
|
|
247
|
+
max_revenue: args.max_revenue
|
|
248
|
+
};
|
|
249
|
+
const results = await finder.findCompanies(query);
|
|
250
|
+
const fieldsArg = args.fields;
|
|
251
|
+
const detail = args.detail;
|
|
252
|
+
const wantsAll = fieldsArg === "all" || detail === "full";
|
|
253
|
+
const slimmedResults = wantsAll ? results : results.map((r) => {
|
|
254
|
+
const fieldList = Array.isArray(fieldsArg) ? fieldsArg : SLIM_COMPANY_FIELDS;
|
|
255
|
+
return applySlimFields(r, fieldList);
|
|
256
|
+
});
|
|
257
|
+
const offset = args.offset ?? 0;
|
|
258
|
+
const has_more = results.length >= effectiveLimit;
|
|
259
|
+
const hints = [];
|
|
260
|
+
if (results.length >= effectiveLimit) {
|
|
261
|
+
hints.push(
|
|
262
|
+
`Returned ${results.length} results (limit reached). Increase limit or use offset for more.`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
const shouldReview = args.review ?? false;
|
|
266
|
+
let tableId;
|
|
267
|
+
let tableUrl;
|
|
268
|
+
if (shouldReview) {
|
|
269
|
+
const tableResult = await createReviewTable(
|
|
270
|
+
`Find Companies: ${query.icp_text ?? query.domain ?? "results"}`,
|
|
271
|
+
slimmedResults
|
|
272
|
+
);
|
|
273
|
+
if (tableResult) {
|
|
274
|
+
tableId = tableResult.table_id;
|
|
275
|
+
tableUrl = tableResult.url;
|
|
276
|
+
} else {
|
|
277
|
+
hints.push("Review table creation failed \u2014 results returned inline only.");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const providerUsed = provider.name;
|
|
281
|
+
const output = args.output;
|
|
282
|
+
if (output === "csv") {
|
|
283
|
+
const filePath = writeResultsCsv("find_companies", slimmedResults);
|
|
284
|
+
return {
|
|
285
|
+
provider: providerUsed,
|
|
286
|
+
count: results.length,
|
|
287
|
+
has_more,
|
|
288
|
+
output: "csv",
|
|
289
|
+
file: filePath,
|
|
290
|
+
preview: slimmedResults.slice(0, 3),
|
|
291
|
+
columns: slimmedResults.length > 0 ? Object.keys(slimmedResults[0]) : [],
|
|
292
|
+
...warnings?.length ? { warnings } : {}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
if (shouldWriteToFile(results.length, output)) {
|
|
296
|
+
const meta = {
|
|
297
|
+
query: {},
|
|
298
|
+
provider: providerUsed,
|
|
299
|
+
count: results.length,
|
|
300
|
+
has_more
|
|
301
|
+
};
|
|
302
|
+
const filePath = writeResultsFile("find_companies", slimmedResults, meta);
|
|
303
|
+
const columns = slimmedResults.length > 0 ? Object.keys(slimmedResults[0]) : [];
|
|
304
|
+
return {
|
|
305
|
+
provider: providerUsed,
|
|
306
|
+
count: results.length,
|
|
307
|
+
has_more,
|
|
308
|
+
output: "file",
|
|
309
|
+
file: filePath,
|
|
310
|
+
preview: slimmedResults.slice(0, 5),
|
|
311
|
+
columns,
|
|
312
|
+
...warnings?.length ? { warnings } : {}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
provider: providerUsed,
|
|
317
|
+
count: results.length,
|
|
318
|
+
has_more,
|
|
319
|
+
offset,
|
|
320
|
+
...tableId && { table_id: tableId, url: tableUrl },
|
|
321
|
+
companies: slimmedResults,
|
|
322
|
+
...warnings?.length ? { warnings } : {},
|
|
323
|
+
...hints.length > 0 && { hints }
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
async function findPeople(args, ctx) {
|
|
327
|
+
const providerName = args.provider || void 0;
|
|
328
|
+
let providers = providerName ? [ctx.registry.get(providerName)].filter(Boolean) : ctx.registry.getForCapability("find_people").filter((p) => getManifest(p.name)?.auto_selectable !== false);
|
|
329
|
+
if (providers.length === 0) {
|
|
330
|
+
const registered = ctx.registry.list().map((p) => p.name);
|
|
331
|
+
if (providerName) {
|
|
332
|
+
const available = registered.length > 0 ? registered.join(", ") : "none";
|
|
333
|
+
throw createGtmError(
|
|
334
|
+
"PROVIDER_NOT_CONFIGURED",
|
|
335
|
+
`Provider "${providerName}" is not registered or does not support people search. Available providers: ${available}.`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
throw createGtmError(
|
|
339
|
+
"PROVIDER_NOT_CONFIGURED",
|
|
340
|
+
"No providers configured for people search. Add a provider with find_people capability."
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
const hasDomain = args.company_domain || args.company_domains;
|
|
344
|
+
if (!providerName && hasDomain) {
|
|
345
|
+
const apollo = providers.find((p) => p?.name === "apollo");
|
|
346
|
+
if (apollo) {
|
|
347
|
+
providers = [apollo, ...providers.filter((p) => p?.name !== "apollo")];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const hasBreadthOnly = !hasDomain && (args.filter_industry || args.min_employees || args.max_employees || args.icp_text || args.revenue_range);
|
|
351
|
+
if (!providerName && hasBreadthOnly) {
|
|
352
|
+
const disco = providers.find((p) => p?.name === "disco");
|
|
353
|
+
if (disco) {
|
|
354
|
+
providers = [disco, ...providers.filter((p) => p?.name !== "disco")];
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const provider = providers[0];
|
|
358
|
+
const finder = provider;
|
|
359
|
+
const effectiveLimit = clampLimit(provider.name, "find_people", args.limit);
|
|
360
|
+
const warnings = providerName ? validateParams(providerName, "find_people", args) : void 0;
|
|
361
|
+
const rawTitle = args.job_title;
|
|
362
|
+
let expandedTitles;
|
|
363
|
+
if (rawTitle) {
|
|
364
|
+
try {
|
|
365
|
+
expandedTitles = await expandTitle(rawTitle);
|
|
366
|
+
} catch (err) {
|
|
367
|
+
logError("prospect:titleExpansion", err instanceof Error ? err : new Error(String(err)));
|
|
368
|
+
expandedTitles = [rawTitle];
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const query = {
|
|
372
|
+
job_title: rawTitle,
|
|
373
|
+
job_titles: expandedTitles && expandedTitles.length > 1 ? expandedTitles : void 0,
|
|
374
|
+
company_name: args.company_name,
|
|
375
|
+
company_domain: args.company_domain,
|
|
376
|
+
location: args.location,
|
|
377
|
+
limit: effectiveLimit,
|
|
378
|
+
seniority: args.seniority,
|
|
379
|
+
department: args.department,
|
|
380
|
+
has_email: args.has_email,
|
|
381
|
+
has_phone: args.has_phone,
|
|
382
|
+
has_linkedin: args.has_linkedin,
|
|
383
|
+
min_connections: args.min_connections,
|
|
384
|
+
offset: args.offset,
|
|
385
|
+
company_domains: args.company_domains,
|
|
386
|
+
skills: args.skills,
|
|
387
|
+
filter_industry: args.filter_industry,
|
|
388
|
+
email_validated: args.email_validated,
|
|
389
|
+
min_employees: args.min_employees,
|
|
390
|
+
max_employees: args.max_employees,
|
|
391
|
+
revenue_range: args.revenue_range
|
|
392
|
+
};
|
|
393
|
+
const results = await finder.findPeople(query);
|
|
394
|
+
const dedupedResults = deduplicatePeople(results);
|
|
395
|
+
const shouldEnrich = args.enrich ?? false;
|
|
396
|
+
let enrichedCount = 0;
|
|
397
|
+
let needsEnrichmentCount = 0;
|
|
398
|
+
if (shouldEnrich) {
|
|
399
|
+
const needsEnrichment = dedupedResults.filter((r) => !r.email || r.email.includes("*"));
|
|
400
|
+
needsEnrichmentCount = needsEnrichment.length;
|
|
401
|
+
if (needsEnrichment.length > 0) {
|
|
402
|
+
try {
|
|
403
|
+
const contacts = needsEnrichment.map((r) => ({
|
|
404
|
+
first_name: r.first_name,
|
|
405
|
+
last_name: r.last_name,
|
|
406
|
+
company: r.company,
|
|
407
|
+
company_domain: r.company_domain,
|
|
408
|
+
linkedin_url: r.linkedin_url
|
|
409
|
+
}));
|
|
410
|
+
const clientConfig = ctx.session.getActiveContext().config;
|
|
411
|
+
const waterfallProviders = clientConfig.waterfall?.email ?? ["prospeo"];
|
|
412
|
+
const waterfallConfig = {
|
|
413
|
+
providers: waterfallProviders,
|
|
414
|
+
find: ["email"],
|
|
415
|
+
verify: false,
|
|
416
|
+
stopOnFirst: true,
|
|
417
|
+
concurrency: 5
|
|
418
|
+
};
|
|
419
|
+
const enrichResults = await runWaterfall(contacts, waterfallConfig, ctx.registry);
|
|
420
|
+
for (let i = 0; i < needsEnrichment.length; i++) {
|
|
421
|
+
const enriched = enrichResults[i];
|
|
422
|
+
if (enriched?.email) {
|
|
423
|
+
needsEnrichment[i].email = enriched.email;
|
|
424
|
+
enrichedCount++;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
} catch (error) {
|
|
428
|
+
logError(
|
|
429
|
+
"prospect:inlineEnrich",
|
|
430
|
+
error instanceof Error ? error : new Error(String(error))
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const resultsWithAlias = dedupedResults.map((r) => ({
|
|
436
|
+
...r,
|
|
437
|
+
company_domain: r.company_domain ?? r.company
|
|
438
|
+
}));
|
|
439
|
+
const fieldsArg = args.fields;
|
|
440
|
+
const detail = args.detail;
|
|
441
|
+
const wantsAll = fieldsArg === "all" || detail === "full";
|
|
442
|
+
const slimmedResults = wantsAll ? resultsWithAlias : resultsWithAlias.map((r) => {
|
|
443
|
+
const fieldList = Array.isArray(fieldsArg) ? fieldsArg : SLIM_PERSON_FIELDS;
|
|
444
|
+
return applySlimFields(r, fieldList);
|
|
445
|
+
});
|
|
446
|
+
const offset = args.offset ?? 0;
|
|
447
|
+
const has_more = dedupedResults.length >= effectiveLimit;
|
|
448
|
+
const hints = [];
|
|
449
|
+
if (expandedTitles && expandedTitles.length > 1) {
|
|
450
|
+
hints.push(`Title expanded: "${rawTitle}" \u2192 ${expandedTitles.map((t) => `"${t}"`).join(", ")}`);
|
|
451
|
+
}
|
|
452
|
+
const maskedCount = dedupedResults.filter((r) => r.email?.includes("*")).length;
|
|
453
|
+
if (maskedCount > 0) {
|
|
454
|
+
hints.push(
|
|
455
|
+
`${maskedCount} emails are masked by the provider. Pass enrich: true to resolve them.`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
if (dedupedResults.length >= effectiveLimit) {
|
|
459
|
+
hints.push(
|
|
460
|
+
`Returned ${dedupedResults.length} results (limit reached). Increase limit or use offset for more.`
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
if (shouldEnrich && enrichedCount < needsEnrichmentCount) {
|
|
464
|
+
const failed = needsEnrichmentCount - enrichedCount;
|
|
465
|
+
hints.push(`${failed} contacts could not be enriched \u2014 missing LinkedIn URL or domain.`);
|
|
466
|
+
}
|
|
467
|
+
const shouldReview = args.review ?? false;
|
|
468
|
+
let tableId;
|
|
469
|
+
let tableUrl;
|
|
470
|
+
if (shouldReview) {
|
|
471
|
+
const tableResult = await createReviewTable(
|
|
472
|
+
`Find People: ${query.job_title ?? query.company_domain ?? "results"}`,
|
|
473
|
+
slimmedResults
|
|
474
|
+
);
|
|
475
|
+
if (tableResult) {
|
|
476
|
+
tableId = tableResult.table_id;
|
|
477
|
+
tableUrl = tableResult.url;
|
|
478
|
+
} else {
|
|
479
|
+
hints.push("Review table creation failed \u2014 results returned inline only.");
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const providerUsed = provider.name;
|
|
483
|
+
const output = args.output;
|
|
484
|
+
if (output === "csv") {
|
|
485
|
+
const filePath = writeResultsCsv("find_people", slimmedResults);
|
|
486
|
+
return {
|
|
487
|
+
provider: providerUsed,
|
|
488
|
+
count: dedupedResults.length,
|
|
489
|
+
has_more,
|
|
490
|
+
output: "csv",
|
|
491
|
+
file: filePath,
|
|
492
|
+
preview: slimmedResults.slice(0, 3),
|
|
493
|
+
columns: slimmedResults.length > 0 ? Object.keys(slimmedResults[0]) : [],
|
|
494
|
+
...warnings?.length ? { warnings } : {}
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
if (shouldWriteToFile(dedupedResults.length, output)) {
|
|
498
|
+
const meta = {
|
|
499
|
+
query: {},
|
|
500
|
+
provider: providerUsed,
|
|
501
|
+
count: dedupedResults.length,
|
|
502
|
+
has_more
|
|
503
|
+
};
|
|
504
|
+
const filePath = writeResultsFile("find_people", slimmedResults, meta);
|
|
505
|
+
const columns = slimmedResults.length > 0 ? Object.keys(slimmedResults[0]) : [];
|
|
506
|
+
return {
|
|
507
|
+
provider: providerUsed,
|
|
508
|
+
count: dedupedResults.length,
|
|
509
|
+
has_more,
|
|
510
|
+
output: "file",
|
|
511
|
+
file: filePath,
|
|
512
|
+
preview: slimmedResults.slice(0, 5),
|
|
513
|
+
columns,
|
|
514
|
+
...warnings?.length ? { warnings } : {}
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
return {
|
|
518
|
+
provider: providerUsed,
|
|
519
|
+
count: dedupedResults.length,
|
|
520
|
+
has_more,
|
|
521
|
+
offset,
|
|
522
|
+
...tableId && { table_id: tableId, url: tableUrl },
|
|
523
|
+
...shouldEnrich && { enriched: enrichedCount },
|
|
524
|
+
people: slimmedResults,
|
|
525
|
+
...warnings?.length ? { warnings } : {},
|
|
526
|
+
...hints.length > 0 && { hints }
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
export {
|
|
530
|
+
handleProspect
|
|
531
|
+
};
|
|
532
|
+
//# sourceMappingURL=prospect-RUOT43H6.js.map
|