autotouch-cli 0.2.59__tar.gz → 0.2.60__tar.gz
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.
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/PKG-INFO +1 -1
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/core/validation.py +62 -6
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/data/CLI_REFERENCE.md +1 -1
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/data/cli-manifest.json +1 -1
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/templates.py +6 -6
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli.egg-info/PKG-INFO +1 -1
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_shared/provider_registry.py +36 -11
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/pyproject.toml +1 -1
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/MANIFEST.in +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/README.md +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/__init__.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/cli.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/cli_contracts.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/commands/__init__.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/commands/auth.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/commands/cells.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/commands/columns.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/commands/jobs.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/commands/leads.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/commands/linkedin.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/commands/prompts.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/commands/rows.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/commands/search.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/commands/sequences.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/commands/tables.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/commands/tasks.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/commands/webhooks.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/commands/workspace_secrets.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/core/__init__.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/core/auth.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/core/config.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/core/csv_import.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/core/http.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/core/io.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/core/output.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/core/polling.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/core/run.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/exceptions.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/mongo_status.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/parser.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/parser_groups.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli/sequence_support.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli.egg-info/SOURCES.txt +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli.egg-info/dependency_links.txt +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli.egg-info/entry_points.txt +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli.egg-info/requires.txt +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_cli.egg-info/top_level.txt +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_shared/__init__.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_shared/linkedin_contract.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/autotouch_shared/search_contract.py +0 -0
- {autotouch_cli-0.2.59 → autotouch_cli-0.2.60}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: autotouch-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.60
|
|
4
4
|
Summary: Autotouch Smart Table CLI
|
|
5
5
|
Project-URL: Homepage, https://app.autotouch.ai
|
|
6
6
|
Project-URL: Documentation, https://github.com/nicolonic/autotouch_main/tree/main/docs/research-table/reference
|
|
@@ -55,8 +55,18 @@ def extract_add_to_crm_field_mappings(payload: Any) -> Optional[Dict[str, Any]]:
|
|
|
55
55
|
return {}
|
|
56
56
|
|
|
57
57
|
|
|
58
|
-
def inspect_add_to_crm_field_mappings(
|
|
59
|
-
|
|
58
|
+
def inspect_add_to_crm_field_mappings(
|
|
59
|
+
field_mappings: Dict[str, Any],
|
|
60
|
+
*,
|
|
61
|
+
require_company_domain: bool = True,
|
|
62
|
+
) -> Dict[str, Any]:
|
|
63
|
+
raw_mode = str(field_mappings.get("mode") or "").strip().lower()
|
|
64
|
+
if raw_mode == "array":
|
|
65
|
+
mode = "array"
|
|
66
|
+
elif raw_mode == "multi":
|
|
67
|
+
mode = "multi"
|
|
68
|
+
else:
|
|
69
|
+
mode = "single"
|
|
60
70
|
required_missing: List[str] = []
|
|
61
71
|
optional_missing: List[str] = []
|
|
62
72
|
|
|
@@ -75,12 +85,20 @@ def inspect_add_to_crm_field_mappings(field_mappings: Dict[str, Any]) -> Dict[st
|
|
|
75
85
|
return True
|
|
76
86
|
return False
|
|
77
87
|
|
|
88
|
+
if not require_company_domain and mode != "single":
|
|
89
|
+
required_missing.append("singleModeOnlyWhenCompanyDomainOptional")
|
|
90
|
+
return {
|
|
91
|
+
"mode": mode,
|
|
92
|
+
"required_missing": required_missing,
|
|
93
|
+
"optional_missing": optional_missing,
|
|
94
|
+
}
|
|
95
|
+
|
|
78
96
|
if mode == "multi":
|
|
79
97
|
common = field_mappings.get("common") if isinstance(field_mappings.get("common"), dict) else {}
|
|
80
98
|
leads = field_mappings.get("leads") if isinstance(field_mappings.get("leads"), list) else []
|
|
81
99
|
|
|
82
100
|
common_domain = str((common or {}).get("companyDomain") or field_mappings.get("companyDomain") or "").strip()
|
|
83
|
-
if not common_domain:
|
|
101
|
+
if require_company_domain and not common_domain:
|
|
84
102
|
required_missing.append("common.companyDomain")
|
|
85
103
|
|
|
86
104
|
has_lead_identity = False
|
|
@@ -95,10 +113,24 @@ def inspect_add_to_crm_field_mappings(field_mappings: Dict[str, Any]) -> Dict[st
|
|
|
95
113
|
optional_missing.append(f"leads[{idx}].{slot}")
|
|
96
114
|
if not has_lead_identity:
|
|
97
115
|
required_missing.append("leads[].identityFields")
|
|
116
|
+
elif mode == "array":
|
|
117
|
+
common = field_mappings.get("common") if isinstance(field_mappings.get("common"), dict) else {}
|
|
118
|
+
template = field_mappings.get("template") if isinstance(field_mappings.get("template"), dict) else {}
|
|
119
|
+
if require_company_domain and not str((common or {}).get("companyDomain") or "").strip():
|
|
120
|
+
required_missing.append("common.companyDomain")
|
|
121
|
+
if not str(field_mappings.get("sourceColumn") or "").strip():
|
|
122
|
+
required_missing.append("sourceColumn")
|
|
123
|
+
if not _has_identity_mapping(template):
|
|
124
|
+
required_missing.append("template.identityFields")
|
|
98
125
|
else:
|
|
99
126
|
company_domain_key = str(field_mappings.get("companyDomain") or "").strip()
|
|
100
|
-
if not company_domain_key:
|
|
127
|
+
if require_company_domain and not company_domain_key:
|
|
101
128
|
required_missing.append("companyDomain")
|
|
129
|
+
if require_company_domain:
|
|
130
|
+
if not _has_identity_mapping(field_mappings):
|
|
131
|
+
required_missing.append("identityFields")
|
|
132
|
+
elif not str(field_mappings.get("linkedinUrl") or "").strip():
|
|
133
|
+
required_missing.append("linkedinUrl")
|
|
102
134
|
if not _has_identity_mapping(field_mappings):
|
|
103
135
|
required_missing.append("identityFields")
|
|
104
136
|
for slot in ["firstName", "lastName", "title", "companyName"]:
|
|
@@ -116,6 +148,7 @@ def validate_add_to_crm_create_payload(payload: Dict[str, Any]) -> None:
|
|
|
116
148
|
field_mappings = extract_add_to_crm_field_mappings(payload)
|
|
117
149
|
if field_mappings is None:
|
|
118
150
|
return
|
|
151
|
+
config = payload.get("config") if isinstance(payload.get("config"), dict) else {}
|
|
119
152
|
|
|
120
153
|
kind_value = str(payload.get("kind") or "").strip().lower()
|
|
121
154
|
if kind_value != "enrichment":
|
|
@@ -130,10 +163,24 @@ def validate_add_to_crm_create_payload(payload: Dict[str, Any]) -> None:
|
|
|
130
163
|
"Tip: run `autotouch columns recipe --type add_to_crm`."
|
|
131
164
|
)
|
|
132
165
|
|
|
133
|
-
|
|
166
|
+
require_company_domain = config.get("requireCompanyDomain", True)
|
|
167
|
+
if not isinstance(require_company_domain, bool):
|
|
168
|
+
raise AutotouchInputError(
|
|
169
|
+
"add_to_crm config.requireCompanyDomain must be a boolean when provided."
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
issues = inspect_add_to_crm_field_mappings(
|
|
173
|
+
field_mappings,
|
|
174
|
+
require_company_domain=require_company_domain,
|
|
175
|
+
)
|
|
134
176
|
required_missing = issues.get("required_missing") or []
|
|
135
177
|
optional_missing = issues.get("optional_missing") or []
|
|
136
178
|
if required_missing:
|
|
179
|
+
if "singleModeOnlyWhenCompanyDomainOptional" in required_missing:
|
|
180
|
+
raise AutotouchInputError(
|
|
181
|
+
"add_to_crm with config.requireCompanyDomain=false only supports single-mode fieldMappings. "
|
|
182
|
+
"Tip: map config.fieldMappings.linkedinUrl and keep mode='single' for LinkedIn-First Sync to Leads."
|
|
183
|
+
)
|
|
137
184
|
missing = ", ".join(str(v) for v in required_missing)
|
|
138
185
|
raise AutotouchInputError(
|
|
139
186
|
f"add_to_crm fieldMappings missing required keys: {missing}. "
|
|
@@ -301,11 +348,20 @@ def run_precheck_for_add_to_crm(
|
|
|
301
348
|
if field_mappings is None:
|
|
302
349
|
return
|
|
303
350
|
|
|
304
|
-
|
|
351
|
+
config = column.get("config") if isinstance(column.get("config"), dict) else {}
|
|
352
|
+
require_company_domain = config.get("requireCompanyDomain", True)
|
|
353
|
+
issues = inspect_add_to_crm_field_mappings(
|
|
354
|
+
field_mappings,
|
|
355
|
+
require_company_domain=bool(require_company_domain) if isinstance(require_company_domain, bool) else True,
|
|
356
|
+
)
|
|
305
357
|
required_missing = issues.get("required_missing") or []
|
|
306
358
|
optional_missing = issues.get("optional_missing") or []
|
|
307
359
|
|
|
308
360
|
if required_missing:
|
|
361
|
+
if "singleModeOnlyWhenCompanyDomainOptional" in required_missing:
|
|
362
|
+
raise AutotouchInputError(
|
|
363
|
+
"add_to_crm column is misconfigured: config.requireCompanyDomain=false only supports single-mode fieldMappings for LinkedIn-First Sync to Leads."
|
|
364
|
+
)
|
|
309
365
|
missing = ", ".join(str(v) for v in required_missing)
|
|
310
366
|
raise AutotouchInputError(
|
|
311
367
|
f"add_to_crm column is misconfigured (missing required mappings: {missing}). "
|
|
@@ -35,11 +35,11 @@ COLUMN_RECIPE_BASE_TYPES = tuple(recipe_types())
|
|
|
35
35
|
COLUMN_RECIPE_ALIASES: Dict[str, Dict[str, Any]] = {
|
|
36
36
|
"add_to_leads": {
|
|
37
37
|
"canonical": "add_to_crm",
|
|
38
|
-
"key": "
|
|
39
|
-
"label": "
|
|
38
|
+
"key": "sync_to_leads",
|
|
39
|
+
"label": "Sync to Leads",
|
|
40
40
|
"notes": [
|
|
41
|
-
"CLI alias for the canonical add_to_crm provider recipe.",
|
|
42
|
-
"
|
|
41
|
+
"Legacy CLI alias for the canonical add_to_crm provider recipe.",
|
|
42
|
+
"Emits the standard Sync to Leads payload. Internal provider/config names remain add_to_crm.",
|
|
43
43
|
],
|
|
44
44
|
},
|
|
45
45
|
"sync_to_leads": {
|
|
@@ -47,8 +47,8 @@ COLUMN_RECIPE_ALIASES: Dict[str, Dict[str, Any]] = {
|
|
|
47
47
|
"key": "sync_to_leads",
|
|
48
48
|
"label": "Sync to Leads",
|
|
49
49
|
"notes": [
|
|
50
|
-
"CLI alias for the canonical add_to_crm provider recipe.",
|
|
51
|
-
"
|
|
50
|
+
"Preferred CLI alias for the canonical add_to_crm provider recipe.",
|
|
51
|
+
"Emits the standard Sync to Leads payload. Internal provider/config names remain add_to_crm.",
|
|
52
52
|
],
|
|
53
53
|
},
|
|
54
54
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: autotouch-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.60
|
|
4
4
|
Summary: Autotouch Smart Table CLI
|
|
5
5
|
Project-URL: Homepage, https://app.autotouch.ai
|
|
6
6
|
Project-URL: Documentation, https://github.com/nicolonic/autotouch_main/tree/main/docs/research-table/reference
|
|
@@ -746,20 +746,21 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
|
|
|
746
746
|
),
|
|
747
747
|
ResearchTableProviderContract(
|
|
748
748
|
key="add_to_crm",
|
|
749
|
-
display_name="
|
|
749
|
+
display_name="Sync to Leads",
|
|
750
750
|
kind="enrichment",
|
|
751
751
|
behavior_class="action_enrichment",
|
|
752
752
|
provider_aliases=("add_to_crm",),
|
|
753
753
|
recipe_type="add_to_crm",
|
|
754
754
|
recipe_payload={
|
|
755
|
-
"key": "
|
|
756
|
-
"label": "
|
|
755
|
+
"key": "sync_to_leads",
|
|
756
|
+
"label": "Sync to Leads",
|
|
757
757
|
"kind": "enrichment",
|
|
758
758
|
"dataType": "json",
|
|
759
759
|
"origin": "manual",
|
|
760
760
|
"autoRun": "onSourceUpdate",
|
|
761
761
|
"config": {
|
|
762
762
|
"provider": "add_to_crm",
|
|
763
|
+
"requireCompanyDomain": True,
|
|
763
764
|
"leadSource": "research_table_export",
|
|
764
765
|
"fieldMappings": {
|
|
765
766
|
"mode": "single",
|
|
@@ -786,18 +787,19 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
|
|
|
786
787
|
},
|
|
787
788
|
recipe_notes=(
|
|
788
789
|
"companyDomain is required; LinkedIn is optional.",
|
|
790
|
+
"Set `config.requireCompanyDomain=false` only for single-mode LinkedIn-first syncs. In that relaxed mode, `linkedinUrl` becomes required and multi/array mode stays unsupported.",
|
|
789
791
|
"Include at least one usable hard-identity mapping such as LinkedIn, email, or phone.",
|
|
790
792
|
"If companyDomain is missing in the table, derive or enrich that domain column first.",
|
|
791
793
|
"Primitive source columns map directly. If a mapped source column stores structured JSON, send an object with column + path, for example {\"column\": \"work_email\", \"path\": \"response\"} or {\"column\": \"mobile_phone\", \"path\": \"mobile_number\"}.",
|
|
792
794
|
"For firstName and lastName, prefer authoritative structured source fields when available, for example {\"column\": \"linkedin_profile_lookup_raw\", \"path\": \"first_name\"} and {\"column\": \"linkedin_profile_lookup_raw\", \"path\": \"last_name\"}. Avoid mapping a combined full-name column directly unless that is an intentional fallback and you accept imperfect CRM data.",
|
|
793
|
-
"
|
|
794
|
-
"Creating add_to_crm after upstream email/phone/lead columns already finished does not replay those older source updates; run
|
|
795
|
+
"Sync to Leads is non-billable.",
|
|
796
|
+
"Creating Sync to Leads (`add_to_crm`) after upstream email/phone/lead columns already finished does not replay those older source updates; run Sync to Leads explicitly or rerun the upstream source column.",
|
|
795
797
|
),
|
|
796
798
|
column_type={
|
|
797
799
|
"type": "add_to_crm",
|
|
798
800
|
"kind": "enrichment",
|
|
799
801
|
"runnable": True,
|
|
800
|
-
"description": "
|
|
802
|
+
"description": "Sync eligible mapped rows into Leads from research-table data.",
|
|
801
803
|
"requirements": {
|
|
802
804
|
"config_provider": "add_to_crm",
|
|
803
805
|
"field_mappings": "required",
|
|
@@ -806,7 +808,7 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
|
|
|
806
808
|
"billing": {
|
|
807
809
|
"billable": False,
|
|
808
810
|
"credits": 0,
|
|
809
|
-
"notes": "
|
|
811
|
+
"notes": "Sync to Leads does not consume credits.",
|
|
810
812
|
},
|
|
811
813
|
},
|
|
812
814
|
setup_contract={
|
|
@@ -971,14 +973,14 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
|
|
|
971
973
|
"config": {
|
|
972
974
|
"provider": "add_to_sequence",
|
|
973
975
|
"sequenceId": "<SEQUENCE_ID>",
|
|
974
|
-
"sourceLeadColumn": "
|
|
976
|
+
"sourceLeadColumn": "sync_to_leads",
|
|
975
977
|
},
|
|
976
978
|
},
|
|
977
979
|
recipe_notes=(
|
|
978
|
-
"sourceLeadColumn should reference the source column key that stores lead IDs (for example
|
|
980
|
+
"sourceLeadColumn should reference the source column key that stores lead IDs (for example sync_to_leads), not the provider name add_to_crm.",
|
|
979
981
|
"Set sequenceId to the target workflow sequence ID.",
|
|
980
982
|
"The target sequence must already be ACTIVE for real enrollment.",
|
|
981
|
-
"Common tail order after contact rows or lead IDs exist is email_finder -> add_to_crm -> create/activate sequence -> add_to_sequence.",
|
|
983
|
+
"Common tail order after contact rows or lead IDs exist is email_finder -> Sync to Leads (`add_to_crm`) -> create/activate sequence -> add_to_sequence.",
|
|
982
984
|
"Creating add_to_sequence after lead IDs already exist does not replay those older updates; run add_to_sequence explicitly or rerun the lead-id source column.",
|
|
983
985
|
),
|
|
984
986
|
column_type={
|
|
@@ -1366,7 +1368,7 @@ def validate_column_provider_contract(
|
|
|
1366
1368
|
raise ProviderContractValidationError(
|
|
1367
1369
|
"missing_required_field",
|
|
1368
1370
|
"add_to_sequence requires config.sourceLeadColumn.",
|
|
1369
|
-
hint="Set sourceLeadColumn to the column key that stores lead IDs, usually
|
|
1371
|
+
hint="Set sourceLeadColumn to the column key that stores lead IDs, usually sync_to_leads.",
|
|
1370
1372
|
)
|
|
1371
1373
|
return contract
|
|
1372
1374
|
|
|
@@ -1378,6 +1380,29 @@ def validate_column_provider_contract(
|
|
|
1378
1380
|
"add_to_crm requires config.fieldMappings.",
|
|
1379
1381
|
hint="Use `autotouch columns recipe --type add_to_crm`.",
|
|
1380
1382
|
)
|
|
1383
|
+
require_company_domain = cfg.get("requireCompanyDomain", True)
|
|
1384
|
+
if not isinstance(require_company_domain, bool):
|
|
1385
|
+
raise ProviderContractValidationError(
|
|
1386
|
+
"invalid_field",
|
|
1387
|
+
"add_to_crm config.requireCompanyDomain must be a boolean when provided.",
|
|
1388
|
+
hint="Use true for the default strict mode or false for single-mode LinkedIn-first sync.",
|
|
1389
|
+
detail={"requireCompanyDomain": require_company_domain},
|
|
1390
|
+
)
|
|
1391
|
+
if require_company_domain is False:
|
|
1392
|
+
mode = _normalize(field_mappings.get("mode")) or "single"
|
|
1393
|
+
if mode in {"multi", "array"}:
|
|
1394
|
+
raise ProviderContractValidationError(
|
|
1395
|
+
"invalid_field",
|
|
1396
|
+
"add_to_crm config.requireCompanyDomain=false only supports single-mode fieldMappings.",
|
|
1397
|
+
hint="Use single-mode sync_to_leads with config.fieldMappings.linkedinUrl when companyDomain is optional.",
|
|
1398
|
+
detail={"mode": field_mappings.get("mode")},
|
|
1399
|
+
)
|
|
1400
|
+
if not _mapping_text(field_mappings.get("linkedinUrl")):
|
|
1401
|
+
raise ProviderContractValidationError(
|
|
1402
|
+
"missing_required_field",
|
|
1403
|
+
"add_to_crm with config.requireCompanyDomain=false requires config.fieldMappings.linkedinUrl.",
|
|
1404
|
+
hint="Map a LinkedIn profile URL column when using LinkedIn-first sync_to_leads.",
|
|
1405
|
+
)
|
|
1381
1406
|
return contract
|
|
1382
1407
|
|
|
1383
1408
|
if contract.key == "sync_to_table":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|