autotouch-cli 0.2.74__tar.gz → 0.2.80__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 (55) hide show
  1. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/PKG-INFO +17 -6
  2. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/README.md +16 -5
  3. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/cli.py +53 -9
  4. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/cli_contracts.py +2 -0
  5. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/linkedin.py +5 -198
  6. autotouch_cli-0.2.80/autotouch_cli/commands/list_build.py +185 -0
  7. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/search.py +6 -6
  8. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/data/CLI_REFERENCE.md +202 -176
  9. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/data/cli-manifest.json +890 -670
  10. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/parser.py +13 -3
  11. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli.egg-info/PKG-INFO +17 -6
  12. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli.egg-info/SOURCES.txt +2 -0
  13. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_shared/linkedin_contract.py +18 -92
  14. autotouch_cli-0.2.80/autotouch_shared/list_build_contract.py +137 -0
  15. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_shared/search_contract.py +13 -13
  16. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/pyproject.toml +1 -1
  17. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/MANIFEST.in +0 -0
  18. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/__init__.py +0 -0
  19. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/__init__.py +0 -0
  20. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/agents.py +0 -0
  21. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/auth.py +0 -0
  22. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/cells.py +0 -0
  23. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/columns.py +0 -0
  24. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/jobs.py +0 -0
  25. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/leads.py +0 -0
  26. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/prompts.py +0 -0
  27. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/rows.py +0 -0
  28. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/sequences.py +0 -0
  29. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/tables.py +0 -0
  30. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/tasks.py +0 -0
  31. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/webhooks.py +0 -0
  32. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/workspace_secrets.py +0 -0
  33. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/__init__.py +0 -0
  34. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/auth.py +0 -0
  35. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/config.py +0 -0
  36. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/csv_import.py +0 -0
  37. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/http.py +0 -0
  38. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/io.py +0 -0
  39. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/output.py +0 -0
  40. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/polling.py +0 -0
  41. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/run.py +0 -0
  42. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/validation.py +0 -0
  43. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/exceptions.py +0 -0
  44. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/mongo_status.py +0 -0
  45. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/parser_groups.py +0 -0
  46. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/sequence_support.py +0 -0
  47. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/templates.py +0 -0
  48. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli.egg-info/dependency_links.txt +0 -0
  49. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli.egg-info/entry_points.txt +0 -0
  50. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli.egg-info/requires.txt +0 -0
  51. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli.egg-info/top_level.txt +0 -0
  52. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_shared/__init__.py +0 -0
  53. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_shared/linkedin_filters.py +0 -0
  54. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_shared/provider_registry.py +0 -0
  55. {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autotouch-cli
3
- Version: 0.2.74
3
+ Version: 0.2.80
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
@@ -132,9 +132,9 @@ autotouch rows get --table-id "$TABLE_ID" --row-id "$ROW_ID" --output json
132
132
  - Inspect rows: `autotouch rows list`, `autotouch rows get`
133
133
  - Inspect one cell: `autotouch cells get`
134
134
  - Create a workflow column: `autotouch columns recipe`, `autotouch columns create`
135
- - Run provider-hidden search: `autotouch search companies`, `autotouch search people`
136
- - Inspect LinkedIn/Sales Nav filters: `autotouch linkedin filters --api sales_navigator --category people`
137
- - Build a paced LinkedIn people list on Smart Table workers: `autotouch linkedin list-build create --company-query 'SaaS startups' --headcount 1-10 --people-query 'Account Executive OR SDR' --num-results 250 --wait`
135
+ - Run neural search for niche/specific company or people discovery: `autotouch search companies`, `autotouch search people`
136
+ - Inspect LinkedIn filters: `autotouch linkedin filters --category people`
137
+ - Build durable company and lead lists on Smart Table workers: `autotouch list-build companies`, `autotouch list-build leads`
138
138
  - Run one LinkedIn search page/debug replay: `autotouch linkedin search`
139
139
  - Run controlled slices: `autotouch columns run-next`
140
140
  - Poll authoritative state: `autotouch jobs get`
@@ -142,14 +142,25 @@ autotouch rows get --table-id "$TABLE_ID" --row-id "$ROW_ID" --output json
142
142
  - Query leads: `autotouch leads query`
143
143
  - Find a lead by email or phone: `autotouch leads query --search '<email-or-phone>' --limit 10`
144
144
 
145
+ ## Neural Search vs Durable List Build
146
+
147
+ Autotouch has two different company/people discovery paths.
148
+
149
+ Use `autotouch search companies` and `autotouch search people` for neural search when the target is niche, semantic, or hard to express with structured filters. Examples: "companies like Clay for healthcare", "AI workflow startups selling to law firms", or "RevOps operators at PLG SaaS companies". Neural company/people search returns at most 10 results per API call and costs 1 credit per API call.
150
+
151
+ Use `autotouch list-build companies` and `autotouch list-build leads` for structured, repeatable list construction. This is the default path for larger company and lead lists using filters like geography IDs, company size, industry IDs, title/persona, and current company IDs. Durable list builds run as background jobs with status/results endpoints and cost 1 credit per successful non-empty result page.
152
+
153
+ For account-first prospecting, build companies first, inspect the returned company IDs, then pass those IDs to `autotouch list-build leads --current-company-id ...`. Do not use neural search as a default pre-step for every list build; use it when the user's target is genuinely semantic or niche.
154
+
145
155
  ## More
146
156
 
147
157
  For automation or agent-driven setup, use:
148
158
  - `autotouch cli-manifest --output json` for the local machine-readable command contract
149
159
  - `autotouch cli-reference` for the shipped parser-generated reference
150
160
  - `autotouch capabilities --output json` for provider/workflow contracts
151
- - `autotouch linkedin filters --output json` for LinkedIn/Sales Navigator filter tokens such as headcount, industry, location, role, function, and seniority
152
- - `autotouch linkedin list-build create` for durable LinkedIn/Sales Navigator list building with Smart Table-owned paced provider requests, visible progress, and cooldown status
161
+ - `autotouch --version` should be `0.2.80` or newer for the 10-result neural search cap and provider-hidden `autotouch list-build ...`; older CLIs may still show the removed `autotouch linkedin list-build` surface
162
+ - `autotouch capabilities --output json --select list_builds` for documented list-build inputs such as geography IDs, company size buckets, profile language, and company IDs
163
+ - `autotouch list-build companies` and `autotouch list-build leads` for durable provider-hidden company and lead list builds with Smart Table-owned background workers, visible progress, and no user-owned network connection requirement
153
164
  - `autotouch linkedin search` for one-page LinkedIn/Sales Navigator replay/debug searches; it is not the recommended path for large lists
154
165
  - `autotouch rows list` / `autotouch rows get` / `autotouch cells get` for read-back and audit
155
166
  - `autotouch sequences ...` and `autotouch tasks ...` for sequence/task workflows
@@ -107,9 +107,9 @@ autotouch rows get --table-id "$TABLE_ID" --row-id "$ROW_ID" --output json
107
107
  - Inspect rows: `autotouch rows list`, `autotouch rows get`
108
108
  - Inspect one cell: `autotouch cells get`
109
109
  - Create a workflow column: `autotouch columns recipe`, `autotouch columns create`
110
- - Run provider-hidden search: `autotouch search companies`, `autotouch search people`
111
- - Inspect LinkedIn/Sales Nav filters: `autotouch linkedin filters --api sales_navigator --category people`
112
- - Build a paced LinkedIn people list on Smart Table workers: `autotouch linkedin list-build create --company-query 'SaaS startups' --headcount 1-10 --people-query 'Account Executive OR SDR' --num-results 250 --wait`
110
+ - Run neural search for niche/specific company or people discovery: `autotouch search companies`, `autotouch search people`
111
+ - Inspect LinkedIn filters: `autotouch linkedin filters --category people`
112
+ - Build durable company and lead lists on Smart Table workers: `autotouch list-build companies`, `autotouch list-build leads`
113
113
  - Run one LinkedIn search page/debug replay: `autotouch linkedin search`
114
114
  - Run controlled slices: `autotouch columns run-next`
115
115
  - Poll authoritative state: `autotouch jobs get`
@@ -117,14 +117,25 @@ autotouch rows get --table-id "$TABLE_ID" --row-id "$ROW_ID" --output json
117
117
  - Query leads: `autotouch leads query`
118
118
  - Find a lead by email or phone: `autotouch leads query --search '<email-or-phone>' --limit 10`
119
119
 
120
+ ## Neural Search vs Durable List Build
121
+
122
+ Autotouch has two different company/people discovery paths.
123
+
124
+ Use `autotouch search companies` and `autotouch search people` for neural search when the target is niche, semantic, or hard to express with structured filters. Examples: "companies like Clay for healthcare", "AI workflow startups selling to law firms", or "RevOps operators at PLG SaaS companies". Neural company/people search returns at most 10 results per API call and costs 1 credit per API call.
125
+
126
+ Use `autotouch list-build companies` and `autotouch list-build leads` for structured, repeatable list construction. This is the default path for larger company and lead lists using filters like geography IDs, company size, industry IDs, title/persona, and current company IDs. Durable list builds run as background jobs with status/results endpoints and cost 1 credit per successful non-empty result page.
127
+
128
+ For account-first prospecting, build companies first, inspect the returned company IDs, then pass those IDs to `autotouch list-build leads --current-company-id ...`. Do not use neural search as a default pre-step for every list build; use it when the user's target is genuinely semantic or niche.
129
+
120
130
  ## More
121
131
 
122
132
  For automation or agent-driven setup, use:
123
133
  - `autotouch cli-manifest --output json` for the local machine-readable command contract
124
134
  - `autotouch cli-reference` for the shipped parser-generated reference
125
135
  - `autotouch capabilities --output json` for provider/workflow contracts
126
- - `autotouch linkedin filters --output json` for LinkedIn/Sales Navigator filter tokens such as headcount, industry, location, role, function, and seniority
127
- - `autotouch linkedin list-build create` for durable LinkedIn/Sales Navigator list building with Smart Table-owned paced provider requests, visible progress, and cooldown status
136
+ - `autotouch --version` should be `0.2.80` or newer for the 10-result neural search cap and provider-hidden `autotouch list-build ...`; older CLIs may still show the removed `autotouch linkedin list-build` surface
137
+ - `autotouch capabilities --output json --select list_builds` for documented list-build inputs such as geography IDs, company size buckets, profile language, and company IDs
138
+ - `autotouch list-build companies` and `autotouch list-build leads` for durable provider-hidden company and lead list builds with Smart Table-owned background workers, visible progress, and no user-owned network connection requirement
128
139
  - `autotouch linkedin search` for one-page LinkedIn/Sales Navigator replay/debug searches; it is not the recommended path for large lists
129
140
  - `autotouch rows list` / `autotouch rows get` / `autotouch cells get` for read-back and audit
130
141
  - `autotouch sequences ...` and `autotouch tasks ...` for sequence/task workflows
@@ -95,9 +95,6 @@ from autotouch_cli.commands.linkedin import (
95
95
  cmd_linkedin_comments as cmd_linkedin_comments_impl,
96
96
  cmd_linkedin_filters as cmd_linkedin_filters_impl,
97
97
  cmd_linkedin_limits as cmd_linkedin_limits_impl,
98
- cmd_linkedin_list_build_create as cmd_linkedin_list_build_create_impl,
99
- cmd_linkedin_list_build_results as cmd_linkedin_list_build_results_impl,
100
- cmd_linkedin_list_build_status as cmd_linkedin_list_build_status_impl,
101
98
  cmd_linkedin_post as cmd_linkedin_post_impl,
102
99
  cmd_linkedin_posts as cmd_linkedin_posts_impl,
103
100
  cmd_linkedin_reactions as cmd_linkedin_reactions_impl,
@@ -106,6 +103,13 @@ from autotouch_cli.commands.linkedin import (
106
103
  cmd_linkedin_search_params as cmd_linkedin_search_params_impl,
107
104
  cmd_linkedin_status as cmd_linkedin_status_impl,
108
105
  )
106
+ from autotouch_cli.commands.list_build import (
107
+ ListBuildCommandRuntime as ListBuildCommandHandlerRuntime,
108
+ cmd_list_build_companies as cmd_list_build_companies_impl,
109
+ cmd_list_build_leads as cmd_list_build_leads_impl,
110
+ cmd_list_build_results as cmd_list_build_results_impl,
111
+ cmd_list_build_status as cmd_list_build_status_impl,
112
+ )
109
113
  from autotouch_cli.commands.rows import (
110
114
  RowCommandRuntime as RowCommandHandlerRuntime,
111
115
  cmd_rows_add as cmd_rows_add_impl,
@@ -353,6 +357,14 @@ from autotouch_shared.provider_registry import (
353
357
  ProviderContractValidationError,
354
358
  validate_column_provider_contract,
355
359
  )
360
+ from autotouch_shared.linkedin_contract import (
361
+ linkedin_capabilities_contract,
362
+ linkedin_endpoint_entries,
363
+ )
364
+ from autotouch_shared.list_build_contract import (
365
+ list_build_capabilities_contract,
366
+ list_build_endpoint_entries,
367
+ )
356
368
  from autotouch_cli.exceptions import (
357
369
  AutotouchAPIError,
358
370
  AutotouchInputError,
@@ -1355,6 +1367,15 @@ def _linkedin_command_runtime() -> LinkedInCommandHandlerRuntime:
1355
1367
  )
1356
1368
 
1357
1369
 
1370
+ def _list_build_command_runtime() -> ListBuildCommandHandlerRuntime:
1371
+ return ListBuildCommandHandlerRuntime(
1372
+ resolve_token=_resolve_token,
1373
+ request_api=_request_api,
1374
+ print_json=lambda data, compact=False: _print_json(data, compact=compact),
1375
+ load_json_input=_load_json_input,
1376
+ )
1377
+
1378
+
1358
1379
  # ---------------------------------------------------------------------------
1359
1380
  # Command registry — replaces ~110 thin shim functions
1360
1381
  # ---------------------------------------------------------------------------
@@ -1549,14 +1570,17 @@ _register("linkedin_limits", cmd_linkedin_limits_impl, _linkedin_command_runtime
1549
1570
  _register("linkedin_search", cmd_linkedin_search_impl, _linkedin_command_runtime)
1550
1571
  _register("linkedin_search_params", cmd_linkedin_search_params_impl, _linkedin_command_runtime)
1551
1572
  _register("linkedin_filters", cmd_linkedin_filters_impl, _linkedin_command_runtime)
1552
- _register("linkedin_list_build_create", cmd_linkedin_list_build_create_impl, _linkedin_command_runtime)
1553
- _register("linkedin_list_build_status", cmd_linkedin_list_build_status_impl, _linkedin_command_runtime)
1554
- _register("linkedin_list_build_results", cmd_linkedin_list_build_results_impl, _linkedin_command_runtime)
1555
1573
  _register("linkedin_posts", cmd_linkedin_posts_impl, _linkedin_command_runtime)
1556
1574
  _register("linkedin_post", cmd_linkedin_post_impl, _linkedin_command_runtime)
1557
1575
  _register("linkedin_comments", cmd_linkedin_comments_impl, _linkedin_command_runtime)
1558
1576
  _register("linkedin_reactions", cmd_linkedin_reactions_impl, _linkedin_command_runtime)
1559
1577
 
1578
+ # -- List build commands --
1579
+ _register("list_build_companies", cmd_list_build_companies_impl, _list_build_command_runtime)
1580
+ _register("list_build_leads", cmd_list_build_leads_impl, _list_build_command_runtime)
1581
+ _register("list_build_status", cmd_list_build_status_impl, _list_build_command_runtime)
1582
+ _register("list_build_results", cmd_list_build_results_impl, _list_build_command_runtime)
1583
+
1560
1584
  # -- Template emitter commands (use _template_runtime) --
1561
1585
  _register("schema", emit_schema, _template_runtime)
1562
1586
  _register("auth_schema", emit_auth_schema, _template_runtime)
@@ -1900,6 +1924,17 @@ def cmd_context_resolved(args: argparse.Namespace) -> None:
1900
1924
  _print_json(data, compact=args.compact)
1901
1925
 
1902
1926
 
1927
+ def _with_bundled_linkedin_capabilities(data: Any) -> Any:
1928
+ if not isinstance(data, dict):
1929
+ return data
1930
+ merged = dict(data)
1931
+ endpoints = merged.get("endpoints") if isinstance(merged.get("endpoints"), dict) else {}
1932
+ merged["endpoints"] = {**endpoints, **linkedin_endpoint_entries(), **list_build_endpoint_entries()}
1933
+ merged["linkedin"] = linkedin_capabilities_contract()
1934
+ merged["list_builds"] = list_build_capabilities_contract()
1935
+ return merged
1936
+
1937
+
1903
1938
  def cmd_capabilities(args: argparse.Namespace) -> None:
1904
1939
  token = _resolve_token(args.token, required=False)
1905
1940
  data = _request_api(
@@ -1911,6 +1946,7 @@ def cmd_capabilities(args: argparse.Namespace) -> None:
1911
1946
  timeout=args.timeout,
1912
1947
  verbose=args.verbose,
1913
1948
  )
1949
+ data = _with_bundled_linkedin_capabilities(data)
1914
1950
  if OUTPUT_MODE == "human" and isinstance(data, dict):
1915
1951
  execution = data.get("execution_policies") if isinstance(data.get("execution_policies"), dict) else {}
1916
1952
  llm = execution.get("llm") if isinstance(execution.get("llm"), dict) else {}
@@ -1953,13 +1989,21 @@ def cmd_capabilities(args: argparse.Namespace) -> None:
1953
1989
  print("")
1954
1990
  print("LinkedIn list building")
1955
1991
  print("---------------------")
1956
- modes = linkedin.get("api_modes", [])
1957
- print(f"API modes : {', '.join(str(m) for m in modes)}")
1992
+ api_mode_management = linkedin.get("api_mode_management")
1993
+ if api_mode_management:
1994
+ print(f"API mode : {api_mode_management}")
1958
1995
  print(f"Access gate : {linkedin.get('access_gate', 'linkedin_access flag')}")
1959
1996
  caps = linkedin.get("rate_limits", {}).get("search_daily_caps", {})
1960
- if caps:
1997
+ if isinstance(caps, dict) and caps:
1961
1998
  for mode, cap in caps.items():
1962
1999
  print(f" {mode:20s}: {cap}")
2000
+ elif caps:
2001
+ print(f"Daily caps : {caps}")
2002
+ summary = linkedin.get("recommendation_summary")
2003
+ if isinstance(summary, list) and summary:
2004
+ print("Recommended path:")
2005
+ for item in summary:
2006
+ print(f" - {item}")
1963
2007
  best_for = linkedin.get("best_for")
1964
2008
  if isinstance(best_for, list) and best_for:
1965
2009
  print("Best for :")
@@ -201,6 +201,8 @@ def action_descriptor(action: argparse.Action) -> Optional[Dict[str, Any]]:
201
201
  return None
202
202
  if isinstance(action, argparse._SubParsersAction):
203
203
  return None
204
+ if getattr(action, "help", None) == argparse.SUPPRESS:
205
+ return None
204
206
 
205
207
  input_kind = _option_input_kind(action)
206
208
  action_kind = _option_action_kind(action)
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  import argparse
4
4
  import json
5
- import time
6
5
  from dataclasses import dataclass
7
6
  from typing import Any, Callable, Dict, List, Optional, Sequence
8
7
 
@@ -60,7 +59,7 @@ def _normalize_linkedin_search_payload(args: argparse.Namespace, *, runtime: Lin
60
59
 
61
60
  payload: Dict[str, Any] = {
62
61
  "category": str(getattr(args, "category", "people") or "people"),
63
- "limit": max(1, min(int(getattr(args, "limit", 25) or 25), 250)),
62
+ "limit": max(1, min(int(getattr(args, "limit", 25) or 25), 100)),
64
63
  }
65
64
  url = str(getattr(args, "url", "") or "").strip()
66
65
  if url:
@@ -177,150 +176,6 @@ def _add_repeated_filter(filters: Dict[str, Any], field: str, values: Optional[L
177
176
  filters[field] = parsed
178
177
 
179
178
 
180
- def _normalize_linkedin_list_build_payload(args: argparse.Namespace, *, runtime: LinkedInCommandRuntime) -> Dict[str, Any]:
181
- explicit = runtime.load_json_input(
182
- inline_json=getattr(args, "data_json", None),
183
- file_path=getattr(args, "data_file", None),
184
- context="data",
185
- default=None,
186
- )
187
- if explicit is not None:
188
- if not isinstance(explicit, dict):
189
- raise AutotouchInputError("linkedin list-build payload must be a JSON object")
190
- return explicit
191
-
192
- company_query = str(getattr(args, "company_query", "") or "").strip()
193
- if not company_query:
194
- raise AutotouchInputError("linkedin list-build create requires --company-query or --data-file")
195
-
196
- payload: Dict[str, Any] = {
197
- "mode": str(getattr(args, "mode", "company-first-people") or "company-first-people").strip(),
198
- "company_query": company_query,
199
- "headcount": str(getattr(args, "headcount", "1-10") or "1-10").strip(),
200
- "num_results": max(1, min(int(getattr(args, "num_results", 100) or 100), 1000)),
201
- }
202
- people_query = str(getattr(args, "people_query", "") or "").strip()
203
- if people_query:
204
- payload["people_query"] = people_query
205
- linkedin_api = str(getattr(args, "linkedin_api", "auto") or "auto").strip()
206
- if linkedin_api and linkedin_api != "auto":
207
- payload["linkedin_api"] = linkedin_api
208
- excluded_titles = _split_repeated_values(getattr(args, "excluded_title", None))
209
- if excluded_titles:
210
- payload["excluded_titles"] = excluded_titles
211
- company_filters = _parse_filter_pairs(getattr(args, "company_filter", None), context="company")
212
- people_filters = _parse_filter_pairs(getattr(args, "people_filter", None), context="people")
213
- _add_repeated_filter(company_filters, "location", getattr(args, "company_location_id", None) or getattr(args, "location_id", None))
214
- _add_repeated_filter(people_filters, "location", getattr(args, "people_location_id", None) or getattr(args, "location_id", None))
215
- _add_repeated_filter(company_filters, "industry", getattr(args, "company_industry_id", None))
216
- _add_repeated_filter(people_filters, "industry", getattr(args, "people_industry_id", None))
217
- _add_repeated_filter(people_filters, "role", getattr(args, "role_id", None))
218
- _add_repeated_filter(people_filters, "function", getattr(args, "function_id", None))
219
- _add_repeated_filter(people_filters, "seniority", getattr(args, "seniority", None))
220
- _add_repeated_filter(people_filters, "profile_language", getattr(args, "profile_language", None))
221
- _add_repeated_filter(company_filters, "technologies", getattr(args, "technology_id", None))
222
- if getattr(args, "has_job_offers", False):
223
- company_filters["has_job_offers"] = True
224
- if company_filters:
225
- payload["company_filters"] = company_filters
226
- if people_filters:
227
- payload["people_filters"] = people_filters
228
- for arg_name, payload_key, context in (
229
- ("extra_params_json", "extra_params", "extra-params"),
230
- ("company_extra_params_json", "company_extra_params", "company-extra-params"),
231
- ("people_extra_params_json", "people_extra_params", "people-extra-params"),
232
- ):
233
- parsed = _load_optional_json_object(getattr(args, arg_name, None), context=context)
234
- if parsed is not None:
235
- payload[payload_key] = parsed
236
- return payload
237
-
238
-
239
- def _wait_for_linkedin_list_build(
240
- job_id: str,
241
- args: argparse.Namespace,
242
- *,
243
- runtime: LinkedInCommandRuntime,
244
- token: Optional[str],
245
- ) -> Dict[str, Any]:
246
- started = time.time()
247
- interval = max(1, int(getattr(args, "poll_interval", 5) or 5))
248
- timeout = max(0, int(getattr(args, "wait_timeout", 0) or 0))
249
- terminal = {"completed", "error", "cancelled"}
250
- latest: Dict[str, Any] = {}
251
-
252
- while True:
253
- latest = runtime.request_api(
254
- "GET",
255
- f"/api/integrations/linkedin/list-builds/{job_id}",
256
- base_url=args.base_url,
257
- token=token,
258
- use_x_api_key=args.use_x_api_key,
259
- timeout=args.timeout,
260
- verbose=args.verbose,
261
- )
262
- status = str(latest.get("status") or "").strip().lower() if isinstance(latest, dict) else ""
263
- if status in terminal:
264
- return latest
265
- if timeout and (time.time() - started) >= timeout:
266
- return latest
267
- time.sleep(interval)
268
-
269
-
270
- def cmd_linkedin_list_build_create(args: argparse.Namespace, *, runtime: LinkedInCommandRuntime) -> None:
271
- token = runtime.resolve_token(args.token, required=True)
272
- payload = _normalize_linkedin_list_build_payload(args, runtime=runtime)
273
- data = runtime.request_api(
274
- "POST",
275
- "/api/integrations/linkedin/list-builds",
276
- base_url=args.base_url,
277
- token=token,
278
- use_x_api_key=args.use_x_api_key,
279
- payload=payload,
280
- timeout=args.timeout,
281
- verbose=args.verbose,
282
- )
283
- if getattr(args, "wait", False) and isinstance(data, dict) and data.get("job_id"):
284
- data = _wait_for_linkedin_list_build(str(data["job_id"]), args, runtime=runtime, token=token)
285
- runtime.print_json(data, args.compact)
286
-
287
-
288
- def cmd_linkedin_list_build_status(args: argparse.Namespace, *, runtime: LinkedInCommandRuntime) -> None:
289
- token = runtime.resolve_token(args.token, required=True)
290
- job_id = str(args.job_id).strip()
291
- data = runtime.request_api(
292
- "GET",
293
- f"/api/integrations/linkedin/list-builds/{job_id}",
294
- base_url=args.base_url,
295
- token=token,
296
- use_x_api_key=args.use_x_api_key,
297
- timeout=args.timeout,
298
- verbose=args.verbose,
299
- )
300
- runtime.print_json(data, args.compact)
301
-
302
-
303
- def cmd_linkedin_list_build_results(args: argparse.Namespace, *, runtime: LinkedInCommandRuntime) -> None:
304
- token = runtime.resolve_token(args.token, required=True)
305
- job_id = str(args.job_id).strip()
306
- params: Dict[str, Any] = {
307
- "type": str(getattr(args, "type", "people") or "people"),
308
- "page": max(1, int(getattr(args, "page", 1) or 1)),
309
- "page_size": max(1, min(int(getattr(args, "page_size", 50) or 50), 500)),
310
- }
311
- data = runtime.request_api(
312
- "GET",
313
- f"/api/integrations/linkedin/list-builds/{job_id}/results",
314
- base_url=args.base_url,
315
- token=token,
316
- use_x_api_key=args.use_x_api_key,
317
- params=params,
318
- timeout=args.timeout,
319
- verbose=args.verbose,
320
- )
321
- runtime.print_json(data, args.compact)
322
-
323
-
324
179
  def cmd_linkedin_filters(args: argparse.Namespace, *, runtime: LinkedInCommandRuntime) -> None:
325
180
  output = linkedin_filter_catalog(
326
181
  api_mode=getattr(args, "api", None),
@@ -450,7 +305,7 @@ def register_linkedin_subcommands(
450
305
  handlers: Dict[str, Callable[[argparse.Namespace], None]],
451
306
  recipe_types: Sequence[str],
452
307
  ) -> None:
453
- parser = subparsers.add_parser("linkedin", help="LinkedIn list-building operations")
308
+ parser = subparsers.add_parser("linkedin", help="Connected LinkedIn search and engagement operations")
454
309
  linkedin_sub = parser.add_subparsers(dest="linkedin_cmd", required=True)
455
310
 
456
311
  recipe_parser = linkedin_sub.add_parser("recipe", help="Print LinkedIn payload recipes for search and list-building")
@@ -473,9 +328,9 @@ def register_linkedin_subcommands(
473
328
  search_parser.add_argument("--url", help="Full LinkedIn/Sales Navigator search URL (overrides keyword filters)")
474
329
  search_parser.add_argument("--category", choices=["people", "companies", "jobs", "posts"], default="people", help="Search category (default: people)")
475
330
  search_parser.add_argument("--keywords", help="Free-text keyword search")
476
- search_parser.add_argument("--linkedin-api", choices=["auto", "classic", "sales_navigator"], default="auto", help="API mode (default: auto; uses connected account mode)")
331
+ search_parser.add_argument("--linkedin-api", choices=["auto", "classic", "sales_navigator"], default="auto", help=argparse.SUPPRESS)
477
332
  search_parser.add_argument("--cursor", help="Pagination cursor from previous response")
478
- search_parser.add_argument("--limit", type=int, default=25, help="Results per page, 1-250 (default: 25)")
333
+ search_parser.add_argument("--limit", type=int, default=25, help="Results per page, 1-100 (default: 25)")
479
334
  search_parser.add_argument("--extra-params-json", help="JSON object with additional search filters (location IDs, industry, etc.)")
480
335
  add_api_common_arguments(search_parser)
481
336
  search_parser.set_defaults(func=handlers["search"])
@@ -483,7 +338,7 @@ def register_linkedin_subcommands(
483
338
  params_parser = linkedin_sub.add_parser("search-params", help="Resolve names to LinkedIn parameter IDs for search filters")
484
339
  params_parser.add_argument("--type", required=True, help="Parameter type: location, industry, company, school, title, language, etc.")
485
340
  params_parser.add_argument("--keywords", default="", help="Search text to match")
486
- params_parser.add_argument("--linkedin-api", choices=["auto", "classic", "sales_navigator"], default="auto", help="Resolver service (default: auto; uses connected account mode)")
341
+ params_parser.add_argument("--linkedin-api", choices=["auto", "classic", "sales_navigator"], default="auto", help=argparse.SUPPRESS)
487
342
  params_parser.add_argument("--limit", type=int, default=25, help="Max results, 1-250 (default: 25)")
488
343
  add_api_common_arguments(params_parser)
489
344
  params_parser.set_defaults(func=handlers["search_params"])
@@ -496,54 +351,6 @@ def register_linkedin_subcommands(
496
351
  add_api_common_arguments(filters_parser)
497
352
  filters_parser.set_defaults(func=handlers["filters"])
498
353
 
499
- list_build_parser = linkedin_sub.add_parser("list-build", help="Create and inspect paced LinkedIn list-build jobs")
500
- list_build_sub = list_build_parser.add_subparsers(dest="linkedin_list_build_cmd", required=True)
501
-
502
- list_build_create = list_build_sub.add_parser("create", help="Create a paced background LinkedIn list-build job")
503
- list_build_create.add_argument("--data-json", help="Explicit list-build payload JSON (overrides individual flags)")
504
- list_build_create.add_argument("--data-file", help="Path to list-build payload JSON file")
505
- list_build_create.add_argument("--mode", choices=["company-first-people"], default="company-first-people", help="List build mode")
506
- list_build_create.add_argument("--linkedin-api", choices=["auto", "classic", "sales_navigator"], default="auto", help="API mode (default: auto; uses connected account mode)")
507
- list_build_create.add_argument("--company-query", help="Company-search query used to build the seed account set")
508
- list_build_create.add_argument("--people-query", help="People-search query/title terms")
509
- list_build_create.add_argument("--headcount", default="1-10", help="Company headcount range, e.g. 1-10 or 11-50")
510
- list_build_create.add_argument("--num-results", type=int, default=100, help="Target number of people results, 1-1000")
511
- list_build_create.add_argument("--excluded-title", action="append", help="Title to exclude after results return; repeat or comma-separate")
512
- list_build_create.add_argument("--location-id", action="append", help="LinkedIn location/region ID applied to company and people search")
513
- list_build_create.add_argument("--company-location-id", action="append", help="LinkedIn location/region ID applied only to company search")
514
- list_build_create.add_argument("--people-location-id", action="append", help="LinkedIn location/region ID applied only to people search")
515
- list_build_create.add_argument("--company-industry-id", action="append", help="LinkedIn industry ID applied to company search")
516
- list_build_create.add_argument("--people-industry-id", action="append", help="LinkedIn industry ID applied to people search")
517
- list_build_create.add_argument("--role-id", action="append", help="Resolved LinkedIn job-title ID for people search; repeat or comma-separate")
518
- list_build_create.add_argument("--function-id", action="append", help="Resolved LinkedIn department/function ID for Sales Nav people search")
519
- list_build_create.add_argument("--seniority", action="append", help="Sales Nav seniority filter value; repeat or comma-separate")
520
- list_build_create.add_argument("--profile-language", action="append", help="Profile language code, e.g. en")
521
- list_build_create.add_argument("--technology-id", action="append", help="Sales Nav technology ID applied to company search")
522
- list_build_create.add_argument("--has-job-offers", action="store_true", help="Filter companies currently hiring on LinkedIn")
523
- list_build_create.add_argument("--company-filter", action="append", help="Catalog company filter as FIELD=VALUE; repeat for multiple filters")
524
- list_build_create.add_argument("--people-filter", action="append", help="Catalog people filter as FIELD=VALUE; repeat for multiple filters")
525
- list_build_create.add_argument("--extra-params-json", help="JSON object applied to both company and people searches")
526
- list_build_create.add_argument("--company-extra-params-json", help="JSON object applied only to company search")
527
- list_build_create.add_argument("--people-extra-params-json", help="JSON object applied only to people search")
528
- list_build_create.add_argument("--wait", action="store_true", help="Poll until the job reaches completed, error, or cancelled")
529
- list_build_create.add_argument("--poll-interval", type=int, default=5, help="Polling interval seconds for --wait")
530
- list_build_create.add_argument("--wait-timeout", type=int, default=0, help="Max seconds to wait (0 = no timeout)")
531
- add_api_common_arguments(list_build_create)
532
- list_build_create.set_defaults(func=handlers["list_build_create"])
533
-
534
- list_build_status = list_build_sub.add_parser("status", help="Get LinkedIn list-build job status")
535
- list_build_status.add_argument("--job-id", required=True, help="LinkedIn list-build job id")
536
- add_api_common_arguments(list_build_status)
537
- list_build_status.set_defaults(func=handlers["list_build_status"])
538
-
539
- list_build_results = list_build_sub.add_parser("results", help="Get LinkedIn list-build results")
540
- list_build_results.add_argument("--job-id", required=True, help="LinkedIn list-build job id")
541
- list_build_results.add_argument("--type", choices=["people", "companies", "all"], default="people", help="Result type")
542
- list_build_results.add_argument("--page", type=int, default=1, help="Results page")
543
- list_build_results.add_argument("--page-size", type=int, default=50, help="Results per page, 1-500")
544
- add_api_common_arguments(list_build_results)
545
- list_build_results.set_defaults(func=handlers["list_build_results"])
546
-
547
354
  posts_parser = linkedin_sub.add_parser("posts", help="List LinkedIn posts for a user")
548
355
  posts_parser.add_argument("--identifier", required=True, help="LinkedIn profile URL, public ID, or URN")
549
356
  posts_parser.add_argument("--cursor", help="Pagination cursor from previous response")