autotouch-cli 0.2.47__tar.gz → 0.2.49__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.
Files changed (46) hide show
  1. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/PKG-INFO +14 -5
  2. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/README.md +13 -4
  3. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/data/CLI_REFERENCE.md +1 -1
  4. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/data/cli-manifest.json +1 -1
  5. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli.egg-info/PKG-INFO +14 -5
  6. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_shared/provider_registry.py +149 -20
  7. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/pyproject.toml +1 -1
  8. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/MANIFEST.in +0 -0
  9. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/__init__.py +0 -0
  10. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/cli.py +0 -0
  11. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/cli_contracts.py +0 -0
  12. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/commands/__init__.py +0 -0
  13. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/commands/auth.py +0 -0
  14. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/commands/cells.py +0 -0
  15. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/commands/columns.py +0 -0
  16. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/commands/jobs.py +0 -0
  17. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/commands/leads.py +0 -0
  18. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/commands/linkedin.py +0 -0
  19. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/commands/prompts.py +0 -0
  20. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/commands/rows.py +0 -0
  21. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/commands/search.py +0 -0
  22. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/commands/sequences.py +0 -0
  23. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/commands/tables.py +0 -0
  24. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/commands/tasks.py +0 -0
  25. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/commands/webhooks.py +0 -0
  26. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/commands/workspace_secrets.py +0 -0
  27. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/core/__init__.py +0 -0
  28. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/core/auth.py +0 -0
  29. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/core/config.py +0 -0
  30. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/core/http.py +0 -0
  31. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/core/io.py +0 -0
  32. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/core/output.py +0 -0
  33. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/core/polling.py +0 -0
  34. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/mongo_status.py +0 -0
  35. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/parser_groups.py +0 -0
  36. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/sequence_support.py +0 -0
  37. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli/templates.py +0 -0
  38. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli.egg-info/SOURCES.txt +0 -0
  39. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli.egg-info/dependency_links.txt +0 -0
  40. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli.egg-info/entry_points.txt +0 -0
  41. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli.egg-info/requires.txt +0 -0
  42. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_cli.egg-info/top_level.txt +0 -0
  43. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_shared/__init__.py +0 -0
  44. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_shared/linkedin_contract.py +0 -0
  45. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/autotouch_shared/search_contract.py +0 -0
  46. {autotouch_cli-0.2.47 → autotouch_cli-0.2.49}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autotouch-cli
3
- Version: 0.2.47
3
+ Version: 0.2.49
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
@@ -146,9 +146,18 @@ For automation or agent-driven setup, use:
146
146
  - `autotouch sequences ...` and `autotouch tasks ...` for sequence/task workflows
147
147
  - `pip install 'autotouch-cli[mongo]'` if you need the Mongo-backed `status` / `watch` commands
148
148
 
149
+ ## LLM Columns
150
+
151
+ For `llm_enrichment` in `agent` mode, the recommended path is:
152
+ - provide `config.instructions`
153
+ - let the API compile the runnable prompt
154
+ - keep `config.useAutoSchema = true`
155
+
156
+ Only send `user_schema` / `response_schema` when you intentionally want to override the generated schema and keep it aligned yourself. The installed recipe surface at `autotouch columns recipe --type llm_enrichment` follows this contract.
157
+
149
158
  ## Docs
150
159
 
151
- - Full CLI reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/research-table/reference/autotouch-cli.md
152
- - Agent playbook: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/research-table/guides/autotouch-cli-agent-playbook.md
153
- - Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/research-table/reference/tables-api.md
154
- - Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/platform/authentication.md
160
+ - Full CLI reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.49/docs/research-table/reference/autotouch-cli.md
161
+ - Agent playbook: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.49/docs/research-table/guides/autotouch-cli-agent-playbook.md
162
+ - Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.49/docs/research-table/reference/tables-api.md
163
+ - Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.49/docs/platform/authentication.md
@@ -121,9 +121,18 @@ For automation or agent-driven setup, use:
121
121
  - `autotouch sequences ...` and `autotouch tasks ...` for sequence/task workflows
122
122
  - `pip install 'autotouch-cli[mongo]'` if you need the Mongo-backed `status` / `watch` commands
123
123
 
124
+ ## LLM Columns
125
+
126
+ For `llm_enrichment` in `agent` mode, the recommended path is:
127
+ - provide `config.instructions`
128
+ - let the API compile the runnable prompt
129
+ - keep `config.useAutoSchema = true`
130
+
131
+ Only send `user_schema` / `response_schema` when you intentionally want to override the generated schema and keep it aligned yourself. The installed recipe surface at `autotouch columns recipe --type llm_enrichment` follows this contract.
132
+
124
133
  ## Docs
125
134
 
126
- - Full CLI reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/research-table/reference/autotouch-cli.md
127
- - Agent playbook: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/research-table/guides/autotouch-cli-agent-playbook.md
128
- - Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/research-table/reference/tables-api.md
129
- - Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/platform/authentication.md
135
+ - Full CLI reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.49/docs/research-table/reference/autotouch-cli.md
136
+ - Agent playbook: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.49/docs/research-table/guides/autotouch-cli-agent-playbook.md
137
+ - Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.49/docs/research-table/reference/tables-api.md
138
+ - Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.49/docs/platform/authentication.md
@@ -1,6 +1,6 @@
1
1
  # Autotouch CLI Reference
2
2
 
3
- Generated from the installed parser for `autotouch-cli` `0.2.47`.
3
+ Generated from the installed parser for `autotouch-cli` `0.2.49`.
4
4
  Manifest schema version: `2`.
5
5
 
6
6
  ## Output Modes
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.2.47",
2
+ "version": "0.2.49",
3
3
  "manifest_schema_version": 2,
4
4
  "entry_points": {
5
5
  "autotouch": "autotouch_cli.cli:main",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autotouch-cli
3
- Version: 0.2.47
3
+ Version: 0.2.49
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
@@ -146,9 +146,18 @@ For automation or agent-driven setup, use:
146
146
  - `autotouch sequences ...` and `autotouch tasks ...` for sequence/task workflows
147
147
  - `pip install 'autotouch-cli[mongo]'` if you need the Mongo-backed `status` / `watch` commands
148
148
 
149
+ ## LLM Columns
150
+
151
+ For `llm_enrichment` in `agent` mode, the recommended path is:
152
+ - provide `config.instructions`
153
+ - let the API compile the runnable prompt
154
+ - keep `config.useAutoSchema = true`
155
+
156
+ Only send `user_schema` / `response_schema` when you intentionally want to override the generated schema and keep it aligned yourself. The installed recipe surface at `autotouch columns recipe --type llm_enrichment` follows this contract.
157
+
149
158
  ## Docs
150
159
 
151
- - Full CLI reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/research-table/reference/autotouch-cli.md
152
- - Agent playbook: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/research-table/guides/autotouch-cli-agent-playbook.md
153
- - Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/research-table/reference/tables-api.md
154
- - Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/platform/authentication.md
160
+ - Full CLI reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.49/docs/research-table/reference/autotouch-cli.md
161
+ - Agent playbook: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.49/docs/research-table/guides/autotouch-cli-agent-playbook.md
162
+ - Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.49/docs/research-table/reference/tables-api.md
163
+ - Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.49/docs/platform/authentication.md
@@ -30,6 +30,7 @@ class ResearchTableProviderContract:
30
30
  key: str
31
31
  display_name: str
32
32
  kind: str
33
+ behavior_class: str = "standard_enrichment"
33
34
  provider_aliases: Tuple[str, ...] = ()
34
35
  origin_aliases: Tuple[str, ...] = ()
35
36
  recipe_type: Optional[str] = None
@@ -39,6 +40,7 @@ class ResearchTableProviderContract:
39
40
  setup_contract: Dict[str, Any] = field(default_factory=dict)
40
41
  execution_policy: Optional[AutoRunExecutionPolicy] = None
41
42
  dispatch_strategy: str = "bulk_queue"
43
+ readiness_strategy: Optional[str] = None
42
44
  save_time_entrypoint: Optional[str] = None
43
45
  is_action_provider: bool = False
44
46
  composition_contract: Dict[str, Any] = field(default_factory=dict)
@@ -72,10 +74,12 @@ class ResearchTableProviderContract:
72
74
  payload: Dict[str, Any] = {
73
75
  "display_name": self.display_name,
74
76
  "kind": self.kind,
77
+ "behavior_class": self.behavior_class,
75
78
  "recipe_type": self.recipe_type,
76
79
  "provider_aliases": list(self.provider_aliases or ()),
77
80
  "origin_aliases": list(self.origin_aliases or ()),
78
81
  "dispatch_strategy": self.dispatch_strategy,
82
+ "readiness_strategy": self.readiness_strategy,
79
83
  "action_provider": bool(self.is_action_provider),
80
84
  "setup_contract": deepcopy(self.setup_contract),
81
85
  "composition_contract": deepcopy(self.composition_contract),
@@ -147,6 +151,12 @@ def _text(value: Any) -> str:
147
151
  return str(value or "").strip()
148
152
 
149
153
 
154
+ def _mapping_text(value: Any) -> str:
155
+ if isinstance(value, dict):
156
+ return _text(value.get("column"))
157
+ return _text(value)
158
+
159
+
150
160
  def _llm_provider_values() -> Tuple[str, ...]:
151
161
  return ("gemini", "xai", "openai", "anthropic", "groq")
152
162
 
@@ -271,6 +281,7 @@ _ADD_TO_SEQUENCE_POLICY = AutoRunExecutionPolicy(
271
281
  force_rerun=False,
272
282
  respect_existing_done=True,
273
283
  default_batch_size=50,
284
+ batch_size_env="ADD_TO_SEQUENCE_BATCH_SIZE",
274
285
  )
275
286
 
276
287
  _HTTP_REQUEST_POLICY = AutoRunExecutionPolicy(
@@ -293,6 +304,7 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
293
304
  key="formatter",
294
305
  display_name="Formatter",
295
306
  kind="formatter",
307
+ behavior_class="formatter",
296
308
  recipe_type="formatter",
297
309
  recipe_payload={
298
310
  "key": "engagement_statement",
@@ -343,6 +355,7 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
343
355
  key="llm",
344
356
  display_name="LLM Enrichment",
345
357
  kind="enrichment",
358
+ behavior_class="standard_enrichment",
346
359
  provider_aliases=_llm_provider_values(),
347
360
  recipe_type="llm_enrichment",
348
361
  recipe_payload={
@@ -365,8 +378,10 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
365
378
  },
366
379
  recipe_notes=(
367
380
  "Recommended default for agent mode: provide instructions and let the API compile the runnable prompt.",
381
+ "When the platform generates the runnable prompt from instructions, keep useAutoSchema=true unless you intentionally want to own the schema yourself.",
382
+ "Do not send user_schema/response_schema in that default generated-prompt path; prompt/schema drift is possible if you later change the generated prompt but keep a frozen custom schema.",
368
383
  "Basic is for extraction, scoring, or classification using fields already in the row. Agent is for tasks that may need outside lookup or web research.",
369
- "Create/update always materializes the runnable prompt into config.prompt/advancedPrompt before execution.",
384
+ "Create/update always materializes the runnable prompt into config.advancedPrompt before execution.",
370
385
  "Generated agent-mode prompts always include company/requester context.",
371
386
  "Basic mode stays manual-prompt-first; write the prompt directly from the user goal/preferences.",
372
387
  "Runtime only injects values for placeholders the prompt explicitly references.",
@@ -431,10 +446,10 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
431
446
  "generated": {
432
447
  "instructions_field": "config.instructions",
433
448
  "prompt_source": "generated",
434
- "materialized_prompt_fields": ["config.prompt", "config.advancedPrompt"],
449
+ "materialized_prompt_fields": ["config.advancedPrompt"],
435
450
  },
436
451
  "manual": {
437
- "prompt_field": "config.prompt",
452
+ "prompt_field": "config.advancedPrompt",
438
453
  "prompt_source": "manual",
439
454
  },
440
455
  },
@@ -477,13 +492,14 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
477
492
  ],
478
493
  },
479
494
  execution_policy=_LLM_POLICY,
480
- dispatch_strategy="llm_scheduler",
495
+ dispatch_strategy="bulk_queue",
481
496
  save_time_entrypoint="provider_save_hook",
482
497
  ),
483
498
  ResearchTableProviderContract(
484
499
  key="email_finder",
485
500
  display_name="Email Finder",
486
501
  kind="enrichment",
502
+ behavior_class="standard_enrichment",
487
503
  provider_aliases=("smart_email_finder", "email_validator", "email_finder"),
488
504
  origin_aliases=("email_finder",),
489
505
  recipe_type="email_finder",
@@ -505,6 +521,7 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
505
521
  },
506
522
  recipe_notes=(
507
523
  "For non-LinkedIn lookup, use lookupStrategy=domain/company with firstName+lastName+domain/company fields.",
524
+ "Primitive source columns map directly. If an upstream source column stores structured JSON, map it as an object with column + path, for example {\"column\": \"linkedin_lookup\", \"path\": \"linkedin_url\"}.",
508
525
  ),
509
526
  column_type={
510
527
  "type": "email_finder",
@@ -531,6 +548,13 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
531
548
  "strategy_field": "config.strategy",
532
549
  "lookup_strategy_field": "config.lookupStrategy",
533
550
  "input_fields": ["config.linkedinUrl", "config.firstName", "config.lastName", "config.company", "config.domain"],
551
+ "structured_source_mapping": {
552
+ "rule": "primitive columns map directly; structured columns use {column, path}",
553
+ "examples": [
554
+ {"linkedinUrl": {"column": "linkedin_lookup", "path": "linkedin_url"}},
555
+ {"emailColumn": {"column": "email_validation", "path": "response"}},
556
+ ],
557
+ },
534
558
  },
535
559
  composition_contract={
536
560
  "consumes": {
@@ -552,6 +576,7 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
552
576
  key="phone_finder",
553
577
  display_name="Phone Finder",
554
578
  kind="enrichment",
579
+ behavior_class="standard_enrichment",
555
580
  provider_aliases=("smart_phone_finder", "phone_validator", "phone_finder"),
556
581
  origin_aliases=("phone_finder",),
557
582
  recipe_type="phone_finder",
@@ -572,6 +597,7 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
572
597
  },
573
598
  recipe_notes=(
574
599
  "You can use linkedinUrl or email/workEmail/personalEmail inputs.",
600
+ "Primitive source columns map directly. If an upstream source column stores structured JSON, map it as an object with column + path, for example {\"column\": \"linkedin_lookup\", \"path\": \"linkedin_url\"}.",
575
601
  ),
576
602
  column_type={
577
603
  "type": "phone_finder",
@@ -605,6 +631,13 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
605
631
  "config.lastName",
606
632
  "config.company",
607
633
  ],
634
+ "structured_source_mapping": {
635
+ "rule": "primitive columns map directly; structured columns use {column, path}",
636
+ "examples": [
637
+ {"linkedinUrl": {"column": "linkedin_lookup", "path": "linkedin_url"}},
638
+ {"phoneColumn": {"column": "mobile_phone", "path": "mobile_number"}},
639
+ ],
640
+ },
608
641
  },
609
642
  composition_contract={
610
643
  "consumes": {
@@ -626,6 +659,7 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
626
659
  key="lead_finder",
627
660
  display_name="Lead Finder",
628
661
  kind="enrichment",
662
+ behavior_class="standard_enrichment",
629
663
  provider_aliases=("lead_finder",),
630
664
  recipe_type="lead_finder",
631
665
  recipe_payload={
@@ -650,6 +684,7 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
650
684
  },
651
685
  recipe_notes=(
652
686
  "Add more targeting keys as needed (jobFunctions, seniorities, keywords, excludeKeywords, skills).",
687
+ "Primitive source columns map directly. If companyDomain or other mapped inputs come from structured JSON, use {\"column\": \"...\", \"path\": \"...\"} instead of a plain string column key.",
653
688
  ),
654
689
  column_type={
655
690
  "type": "lead_finder",
@@ -675,6 +710,12 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
675
710
  "source_mode_field": "config.sourceMode",
676
711
  "company_domain_field": "config.companyDomain",
677
712
  "max_results_field": "config.maxResults",
713
+ "structured_source_mapping": {
714
+ "rule": "primitive columns map directly; structured columns use {column, path}",
715
+ "examples": [
716
+ {"companyDomain": {"column": "company_lookup", "path": "domain"}},
717
+ ],
718
+ },
678
719
  },
679
720
  composition_contract={
680
721
  "consumes": {
@@ -706,6 +747,7 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
706
747
  key="add_to_crm",
707
748
  display_name="Add to Leads",
708
749
  kind="enrichment",
750
+ behavior_class="action_enrichment",
709
751
  provider_aliases=("add_to_crm",),
710
752
  recipe_type="add_to_crm",
711
753
  recipe_payload={
@@ -745,7 +787,7 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
745
787
  "companyDomain is required; LinkedIn is optional.",
746
788
  "Include at least one usable hard-identity mapping such as LinkedIn, email, or phone.",
747
789
  "If companyDomain is missing in the table, derive or enrich that domain column first.",
748
- "Primitive source columns map directly. Structured JSON columns must specify an explicit path such as work_email.response or mobile_phone.mobile_number.",
790
+ "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\"}.",
749
791
  "Add to CRM is optional and non-billable.",
750
792
  "Creating add_to_crm after upstream email/phone/lead columns already finished does not replay those older source updates; run add_to_crm explicitly or rerun the upstream source column.",
751
793
  ),
@@ -772,18 +814,19 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
772
814
  "eligibility": "company domain plus at least one valid LinkedIn URL, email, or phone must resolve from mapped source columns",
773
815
  "replay_behavior": "source updates only; explicit run required to backfill older upstream results",
774
816
  "structured_source_mapping": {
775
- "rule": "primitive columns map directly; structured columns require explicit path",
776
- "field": "config.fieldMappings.*.path",
817
+ "rule": "primitive columns map directly; structured columns use {column, path}",
818
+ "field": "config.fieldMappings.*",
777
819
  "examples": [
778
- "emailAddresses[].path = response",
779
- "phoneNumbers[].path = mobile_number",
820
+ {"emailAddresses": [{"column": "work_email", "path": "response", "type": "work"}]},
821
+ {"phoneNumbers": [{"column": "mobile_phone", "path": "mobile_number", "type": "mobile"}]},
822
+ {"linkedinUrl": {"column": "linkedin_lookup", "path": "linkedin_url"}},
780
823
  ],
781
824
  },
782
825
  },
783
826
  composition_contract={
784
827
  "consumes": {
785
828
  "kind": "crm_export_inputs",
786
- "shape": "mapped source row values including company domain plus LinkedIn/email/phone identity; structured source columns must identify the exact JSON key via path",
829
+ "shape": "mapped source row values including company domain plus LinkedIn/email/phone identity; structured source columns use explicit {column, path} mappings to identify the exact field",
787
830
  },
788
831
  "produces": {
789
832
  "kind": "lead_ids",
@@ -794,12 +837,14 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
794
837
  ],
795
838
  },
796
839
  execution_policy=_ADD_TO_CRM_POLICY,
840
+ readiness_strategy="add_to_crm",
797
841
  is_action_provider=True,
798
842
  ),
799
843
  ResearchTableProviderContract(
800
844
  key="sync_to_table",
801
845
  display_name="Sync to Table",
802
846
  kind="enrichment",
847
+ behavior_class="action_enrichment",
803
848
  provider_aliases=("sync_to_table",),
804
849
  recipe_type="sync_to_table",
805
850
  recipe_payload={
@@ -904,12 +949,14 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
904
949
  },
905
950
  execution_policy=_SYNC_TO_TABLE_POLICY,
906
951
  dispatch_strategy="sync_to_table_prefilter",
952
+ readiness_strategy="sync_to_table",
907
953
  is_action_provider=True,
908
954
  ),
909
955
  ResearchTableProviderContract(
910
956
  key="add_to_sequence",
911
957
  display_name="Add to Sequence",
912
958
  kind="enrichment",
959
+ behavior_class="action_enrichment",
913
960
  provider_aliases=("add_to_sequence",),
914
961
  recipe_type="add_to_sequence",
915
962
  recipe_payload={
@@ -972,12 +1019,14 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
972
1019
  ],
973
1020
  },
974
1021
  execution_policy=_ADD_TO_SEQUENCE_POLICY,
1022
+ readiness_strategy="add_to_sequence",
975
1023
  is_action_provider=True,
976
1024
  ),
977
1025
  ResearchTableProviderContract(
978
1026
  key="http_request",
979
1027
  display_name="HTTP Request",
980
1028
  kind="enrichment",
1029
+ behavior_class="standard_enrichment",
981
1030
  provider_aliases=("http_request",),
982
1031
  recipe_type="http_request",
983
1032
  recipe_payload={
@@ -998,7 +1047,7 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
998
1047
  },
999
1048
  recipe_notes=(
1000
1049
  "Use {{column_key}} placeholders in config.url, config.headers, and config.body to reference row values.",
1001
- "Use {{secrets.name}} to resolve a workspace secret or active org integration credential by provider slug.",
1050
+ "Use {{secrets.name}} to resolve a workspace secret by name.",
1002
1051
  "body can be null, a string template, or an object whose values are templated and sent as JSON.",
1003
1052
  "The stored cell value is the parsed response body (JSON or text), not the full transport envelope.",
1004
1053
  "Preview the resolved request with `autotouch columns test-http-request --table-id <TABLE_ID> --data-file column.json` before creating or running the column.",
@@ -1038,7 +1087,7 @@ _PROVIDER_CONTRACTS: Tuple[ResearchTableProviderContract, ...] = (
1038
1087
  composition_contract={
1039
1088
  "consumes": {
1040
1089
  "kind": "row_data",
1041
- "shape": "{{column_key}} templates resolved from row cell values plus optional {{secrets.name}} org credentials",
1090
+ "shape": "{{column_key}} templates resolved from row cell values plus optional {{secrets.name}} workspace secrets",
1042
1091
  },
1043
1092
  "produces": {
1044
1093
  "kind": "json",
@@ -1257,30 +1306,39 @@ def validate_column_provider_contract(
1257
1306
 
1258
1307
  if contract.key == "email_finder":
1259
1308
  if not any(
1260
- _text(cfg.get(field_name))
1261
- for field_name in ("linkedinUrl", "firstName", "lastName", "company", "domain")
1309
+ _mapping_text(cfg.get(field_name))
1310
+ for field_name in ("linkedinUrl", "firstName", "lastName", "company", "domain", "emailColumn")
1262
1311
  ):
1263
1312
  raise ProviderContractValidationError(
1264
1313
  "missing_required_field",
1265
- "email_finder requires at least one lookup input such as config.linkedinUrl, config.domain, or name/company fields.",
1314
+ "email_finder requires at least one lookup input such as config.linkedinUrl, config.domain, config.emailColumn, or name/company fields.",
1266
1315
  hint="Use `autotouch columns recipe --type email_finder`.",
1267
1316
  )
1268
1317
  return contract
1269
1318
 
1270
1319
  if contract.key == "phone_finder":
1271
1320
  if not any(
1272
- _text(cfg.get(field_name))
1273
- for field_name in ("linkedinUrl", "email", "workEmail", "personalEmail", "firstName", "lastName", "company")
1321
+ _mapping_text(cfg.get(field_name))
1322
+ for field_name in (
1323
+ "linkedinUrl",
1324
+ "email",
1325
+ "workEmail",
1326
+ "personalEmail",
1327
+ "firstName",
1328
+ "lastName",
1329
+ "company",
1330
+ "phoneColumn",
1331
+ )
1274
1332
  ):
1275
1333
  raise ProviderContractValidationError(
1276
1334
  "missing_required_field",
1277
- "phone_finder requires at least one lookup input such as config.linkedinUrl or config.email/workEmail.",
1335
+ "phone_finder requires at least one lookup input such as config.linkedinUrl, config.email/workEmail, or config.phoneColumn.",
1278
1336
  hint="Use `autotouch columns recipe --type phone_finder`.",
1279
1337
  )
1280
1338
  return contract
1281
1339
 
1282
1340
  if contract.key == "lead_finder":
1283
- if not _text(cfg.get("companyDomain")):
1341
+ if not _mapping_text(cfg.get("companyDomain")):
1284
1342
  raise ProviderContractValidationError(
1285
1343
  "missing_required_field",
1286
1344
  "lead_finder requires config.companyDomain.",
@@ -1308,7 +1366,7 @@ def validate_column_provider_contract(
1308
1366
  raise ProviderContractValidationError(
1309
1367
  "invalid_field",
1310
1368
  "http_request config.headers must be an object when provided.",
1311
- hint="Set config.headers to a JSON object such as {\"Authorization\": \"Bearer {{secrets.provider}}\"}.",
1369
+ hint="Set config.headers to a JSON object such as {\"Authorization\": \"Bearer {{secrets.openai_api_key}}\"}.",
1312
1370
  )
1313
1371
  body = cfg.get("body")
1314
1372
  if body is not None and not isinstance(body, (str, dict)):
@@ -1355,3 +1413,74 @@ def automation_workflow_blueprints() -> Dict[str, Dict[str, Any]]:
1355
1413
 
1356
1414
  def action_provider_keys() -> Tuple[str, ...]:
1357
1415
  return tuple(contract.key for contract in _PROVIDER_CONTRACTS if contract.is_action_provider)
1416
+
1417
+
1418
+ def behavior_class_keys(behavior_class: str) -> Tuple[str, ...]:
1419
+ normalized = _normalize(behavior_class)
1420
+ return tuple(
1421
+ contract.key
1422
+ for contract in _PROVIDER_CONTRACTS
1423
+ if _normalize(contract.behavior_class) == normalized
1424
+ )
1425
+
1426
+
1427
+ def orchestrated_runtime_provider_keys(*, include_formatter: bool = False) -> Tuple[str, ...]:
1428
+ providers: List[str] = []
1429
+ for contract in _PROVIDER_CONTRACTS:
1430
+ policy = contract.execution_policy
1431
+ if not policy:
1432
+ continue
1433
+ normalized_behavior = _normalize(contract.behavior_class)
1434
+ if normalized_behavior == "formatter":
1435
+ if include_formatter:
1436
+ providers.append(policy.orchestrator_provider)
1437
+ continue
1438
+ if contract.key == "llm":
1439
+ providers.extend(["gemini", "xai"])
1440
+ continue
1441
+ providers.append(policy.orchestrator_provider)
1442
+ return tuple(dict.fromkeys(provider for provider in providers if provider))
1443
+
1444
+
1445
+ def resolve_provider_behavior_class(
1446
+ column: Dict[str, Any],
1447
+ config: Optional[Dict[str, Any]],
1448
+ ) -> Optional[str]:
1449
+ contract = resolve_effective_provider_contract(column, config)
1450
+ if not contract:
1451
+ return None
1452
+ return contract.behavior_class
1453
+
1454
+
1455
+ def should_resolve_managed_llm_model(
1456
+ column: Dict[str, Any],
1457
+ config: Optional[Dict[str, Any]],
1458
+ ) -> bool:
1459
+ contract = resolve_effective_provider_contract(column, config)
1460
+ if not contract:
1461
+ return False
1462
+ return contract.key == "llm" and _normalize(column.get("origin")) == "ai"
1463
+
1464
+
1465
+ def resolve_runtime_orchestrator_provider(
1466
+ column: Dict[str, Any],
1467
+ config: Optional[Dict[str, Any]],
1468
+ ) -> Optional[str]:
1469
+ contract = resolve_effective_provider_contract(column, config)
1470
+ if not contract or not contract.execution_policy:
1471
+ return None
1472
+ if _normalize(contract.behavior_class) == "formatter":
1473
+ return None
1474
+ if contract.key == "llm":
1475
+ runtime_provider = _normalize((config or {}).get("provider"))
1476
+ if runtime_provider in {"gemini", "xai"}:
1477
+ return runtime_provider
1478
+ return None
1479
+ return contract.execution_policy.orchestrator_provider
1480
+
1481
+
1482
+ def uses_orchestrated_bulk_run(
1483
+ column: Dict[str, Any],
1484
+ config: Optional[Dict[str, Any]],
1485
+ ) -> bool:
1486
+ return resolve_runtime_orchestrator_provider(column, config) is not None
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "autotouch-cli"
7
- version = "0.2.47"
7
+ version = "0.2.49"
8
8
  description = "Autotouch Smart Table CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
File without changes