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.
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/PKG-INFO +17 -6
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/README.md +16 -5
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/cli.py +53 -9
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/cli_contracts.py +2 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/linkedin.py +5 -198
- autotouch_cli-0.2.80/autotouch_cli/commands/list_build.py +185 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/search.py +6 -6
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/data/CLI_REFERENCE.md +202 -176
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/data/cli-manifest.json +890 -670
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/parser.py +13 -3
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli.egg-info/PKG-INFO +17 -6
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli.egg-info/SOURCES.txt +2 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_shared/linkedin_contract.py +18 -92
- autotouch_cli-0.2.80/autotouch_shared/list_build_contract.py +137 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_shared/search_contract.py +13 -13
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/pyproject.toml +1 -1
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/MANIFEST.in +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/__init__.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/__init__.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/agents.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/auth.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/cells.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/columns.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/jobs.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/leads.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/prompts.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/rows.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/sequences.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/tables.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/tasks.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/webhooks.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/commands/workspace_secrets.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/__init__.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/auth.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/config.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/csv_import.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/http.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/io.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/output.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/polling.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/run.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/core/validation.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/exceptions.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/mongo_status.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/parser_groups.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/sequence_support.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli/templates.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli.egg-info/dependency_links.txt +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli.egg-info/entry_points.txt +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli.egg-info/requires.txt +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_cli.egg-info/top_level.txt +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_shared/__init__.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_shared/linkedin_filters.py +0 -0
- {autotouch_cli-0.2.74 → autotouch_cli-0.2.80}/autotouch_shared/provider_registry.py +0 -0
- {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.
|
|
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
|
|
136
|
-
- Inspect LinkedIn
|
|
137
|
-
- Build
|
|
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
|
|
152
|
-
- `autotouch
|
|
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
|
|
111
|
-
- Inspect LinkedIn
|
|
112
|
-
- Build
|
|
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
|
|
127
|
-
- `autotouch
|
|
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
|
-
|
|
1957
|
-
|
|
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),
|
|
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
|
|
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=
|
|
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-
|
|
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=
|
|
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")
|