discolike-cli 0.3.0__tar.gz → 0.3.2__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.
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/PKG-INFO +2 -2
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/pyproject.toml +2 -2
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/auth.py +32 -5
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/company.py +10 -5
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/contacts.py +149 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/discover.py +89 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/enrich.py +11 -3
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/main.py +2 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/match.py +25 -1
- discolike_cli-0.3.2/src/discolike_cli/signup.py +76 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/tests/test_auth_oauth.py +91 -5
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/tests/test_company.py +24 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/tests/test_contacts_cli.py +162 -9
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/tests/test_discover.py +114 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/tests/test_enrich_cli.py +37 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/tests/test_match_cli.py +55 -0
- discolike_cli-0.3.2/tests/test_sdk_parity.py +99 -0
- discolike_cli-0.3.2/tests/test_signup_cli.py +104 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/.gitignore +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/LICENSE +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/README.md +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/__init__.py +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/_loopback.py +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/_output.py +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/account.py +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/discogen.py +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/email.py +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/providers.py +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/py.typed +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/src/discolike_cli/queries.py +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/tests/conftest.py +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/tests/test_account.py +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/tests/test_auth.py +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/tests/test_discogen_cli.py +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/tests/test_email_cli.py +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/tests/test_output.py +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/tests/test_providers_cli.py +0 -0
- {discolike_cli-0.3.0 → discolike_cli-0.3.2}/tests/test_queries_cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: discolike-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Official CLI for the DiscoLike API
|
|
5
5
|
Project-URL: Homepage, https://www.discolike.com
|
|
6
6
|
Project-URL: Documentation, https://docs.discolike.com
|
|
@@ -25,7 +25,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
25
25
|
Classifier: Topic :: Utilities
|
|
26
26
|
Classifier: Typing :: Typed
|
|
27
27
|
Requires-Python: >=3.10
|
|
28
|
-
Requires-Dist: discolike==0.3.
|
|
28
|
+
Requires-Dist: discolike==0.3.2
|
|
29
29
|
Requires-Dist: rich>=13.0
|
|
30
30
|
Requires-Dist: typer>=0.12
|
|
31
31
|
Description-Content-Type: text/markdown
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "discolike-cli"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "Official CLI for the DiscoLike API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -12,7 +12,7 @@ license-files = ["LICENSE"]
|
|
|
12
12
|
requires-python = ">=3.10"
|
|
13
13
|
authors = [{ name = "DiscoLike", email = "support@discolike.com" }]
|
|
14
14
|
dependencies = [
|
|
15
|
-
"discolike==0.3.
|
|
15
|
+
"discolike==0.3.2",
|
|
16
16
|
"typer>=0.12",
|
|
17
17
|
"rich>=13.0",
|
|
18
18
|
]
|
|
@@ -33,11 +33,12 @@ from discolike._oauth import OAuthError
|
|
|
33
33
|
from discolike._oauth import build_authorization_url
|
|
34
34
|
from discolike._oauth import discover
|
|
35
35
|
from discolike._oauth import exchange_code
|
|
36
|
-
from discolike._oauth import pkce_pair
|
|
37
36
|
from discolike._oauth import register_client
|
|
38
37
|
from discolike_cli._loopback import CallbackServer
|
|
39
38
|
from discolike_cli._output import emit
|
|
40
39
|
from discolike_cli._output import handle_errors
|
|
40
|
+
from discolike_cli.signup import _is_interactive
|
|
41
|
+
from discolike_cli.signup import run_signup
|
|
41
42
|
|
|
42
43
|
app = typer.Typer(help="Manage credentials: log in (browser or API key), check status, log out.")
|
|
43
44
|
|
|
@@ -55,6 +56,10 @@ LOGIN_METHODS = (AUTH_METHOD_OAUTH, AUTH_METHOD_API_KEY)
|
|
|
55
56
|
DEAD_CLIENT_ERRORS = frozenset({"invalid_client", "unauthorized_client"})
|
|
56
57
|
|
|
57
58
|
|
|
59
|
+
HAS_ACCOUNT_PROMPT = "Do you already have a DiscoLike account?"
|
|
60
|
+
SIGNUP_FOLLOWUP_MESSAGE = "Confirm the email, then run `discolike auth login` again to sign in."
|
|
61
|
+
|
|
62
|
+
|
|
58
63
|
class _DeadClientError(Exception):
|
|
59
64
|
"""The authorization server no longer recognises the registered client_id."""
|
|
60
65
|
|
|
@@ -63,6 +68,23 @@ def _mask(key: str) -> str:
|
|
|
63
68
|
return "…" + key[-MASKED_VISIBLE_CHARS:]
|
|
64
69
|
|
|
65
70
|
|
|
71
|
+
def _was_passed_on_command_line(ctx: typer.Context, name: str) -> bool:
|
|
72
|
+
source = ctx.get_parameter_source(name)
|
|
73
|
+
return source is not None and source.name == "COMMANDLINE"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _offer_signup(ctx: typer.Context) -> None:
|
|
77
|
+
email = typer.prompt("Work email")
|
|
78
|
+
first_name = typer.prompt("First name")
|
|
79
|
+
last_name = typer.prompt("Last name")
|
|
80
|
+
base_url = str(ctx.obj.get("base_url") or DEFAULT_BASE_URL).rstrip("/")
|
|
81
|
+
run_signup(
|
|
82
|
+
email=email, first_name=first_name, last_name=last_name, agent=None, base_url=base_url, yes=False, fmt=None
|
|
83
|
+
)
|
|
84
|
+
typer.echo(SIGNUP_FOLLOWUP_MESSAGE)
|
|
85
|
+
raise typer.Exit(code=0)
|
|
86
|
+
|
|
87
|
+
|
|
66
88
|
def _iso(epoch_seconds: float) -> str:
|
|
67
89
|
return datetime.fromtimestamp(epoch_seconds, tz=timezone.utc).isoformat()
|
|
68
90
|
|
|
@@ -135,13 +157,11 @@ def _authorize(
|
|
|
135
157
|
open_browser: bool,
|
|
136
158
|
http: httpx2.Client,
|
|
137
159
|
) -> OAuthCredential:
|
|
138
|
-
verifier, challenge = pkce_pair()
|
|
139
160
|
state = secrets.token_urlsafe(STATE_BYTES)
|
|
140
|
-
url = build_authorization_url(
|
|
161
|
+
url, verifier = build_authorization_url(
|
|
141
162
|
metadata,
|
|
142
163
|
client_id=registration.client_id,
|
|
143
164
|
redirect_uri=registration.redirect_uri,
|
|
144
|
-
code_challenge=challenge,
|
|
145
165
|
state=state,
|
|
146
166
|
resource=resource,
|
|
147
167
|
)
|
|
@@ -168,7 +188,6 @@ def _authorize(
|
|
|
168
188
|
code_verifier=verifier,
|
|
169
189
|
redirect_uri=registration.redirect_uri,
|
|
170
190
|
resource=resource,
|
|
171
|
-
client=http,
|
|
172
191
|
)
|
|
173
192
|
except OAuthError as exc:
|
|
174
193
|
if exc.error in DEAD_CLIENT_ERRORS:
|
|
@@ -230,6 +249,14 @@ def login(
|
|
|
230
249
|
if method not in LOGIN_METHODS:
|
|
231
250
|
raise typer.BadParameter(f"must be one of {', '.join(LOGIN_METHODS)}", param_hint="--method")
|
|
232
251
|
global_key_passed = ctx.obj.get("api_key") is not None and _global_key_source(ctx) == SOURCE_OPTION
|
|
252
|
+
if (
|
|
253
|
+
_is_interactive()
|
|
254
|
+
and api_key is None
|
|
255
|
+
and not global_key_passed
|
|
256
|
+
and not _was_passed_on_command_line(ctx, "method")
|
|
257
|
+
and not typer.confirm(HAS_ACCOUNT_PROMPT, default=True)
|
|
258
|
+
):
|
|
259
|
+
_offer_signup(ctx)
|
|
233
260
|
if api_key or global_key_passed or method == AUTH_METHOD_API_KEY:
|
|
234
261
|
_api_key_login(ctx, api_key=api_key)
|
|
235
262
|
return
|
|
@@ -134,12 +134,17 @@ def public_links(
|
|
|
134
134
|
@handle_errors
|
|
135
135
|
def extract_command(
|
|
136
136
|
ctx: typer.Context,
|
|
137
|
-
url: str = typer.Argument(
|
|
137
|
+
url: str | None = typer.Argument(None, help="Page URL to extract content from"),
|
|
138
|
+
domain: str | None = typer.Option(
|
|
139
|
+
None, "--domain", help="Bare domain to extract instead of URL (alias for https://DOMAIN, served from cache)."
|
|
140
|
+
),
|
|
138
141
|
fmt: str | None = typer.Option(None, "--format", help=FORMAT_HELP),
|
|
139
142
|
) -> None:
|
|
140
|
-
"""Extract page content from a URL."""
|
|
143
|
+
"""Extract page content from a URL or a bare domain."""
|
|
141
144
|
from discolike_cli.main import get_client
|
|
142
145
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
if (url is None) == (domain is None):
|
|
147
|
+
raise typer.BadParameter("Provide exactly one of URL or --domain")
|
|
148
|
+
|
|
149
|
+
request = build_request(CompaniesExtractParams, _merge_params(None, url=url, domain=domain))
|
|
150
|
+
emit(get_client(ctx).companies.extract(request), fmt=fmt)
|
|
@@ -25,6 +25,25 @@ WAIT_HELP = "Block until the job finishes, streaming progress to stderr."
|
|
|
25
25
|
TIMEOUT_HELP = "Max seconds to wait with --wait."
|
|
26
26
|
PARAM_HELP = "Extra key=value query parameter forwarded to the SDK (repeatable)."
|
|
27
27
|
JOBSTART_DATE_HELP = "Job start date filter: min date or 'min,max' range, e.g. 2025-01-01 or 2025-01-01,2025-06-30."
|
|
28
|
+
SUMMARY_HELP = "Filter by profile summary text (semantic search)."
|
|
29
|
+
NEGATE_SUMMARY_HELP = "Exclude contacts matching this summary description."
|
|
30
|
+
NAME_HELP = "Filter by contact name (partial match supported)."
|
|
31
|
+
SKILLS_HELP = "Filter by skill (repeatable)."
|
|
32
|
+
FILTER_STATE_HELP = "Filter by company state/region (repeatable)."
|
|
33
|
+
NEGATE_FILTER_STATE_HELP = "Exclude contacts at companies in this state (repeatable)."
|
|
34
|
+
PERSON_STATE_HELP = "Filter by contact state/region (repeatable)."
|
|
35
|
+
PERSONA_ID_HELP = "Filter by persona ID (repeatable)."
|
|
36
|
+
EMAIL_VALIDATED_HELP = "Only contacts with (or without) a validated email address."
|
|
37
|
+
HAS_PHONE_HELP = "Only contacts with (or without) a phone number."
|
|
38
|
+
HAS_MOBILE_HELP = "Only contacts with (or without) a mobile phone number."
|
|
39
|
+
HAS_LINKEDIN_HELP = "Only contacts with (or without) a LinkedIn profile."
|
|
40
|
+
MIN_CONNECTIONS_HELP = "Minimum LinkedIn connections required."
|
|
41
|
+
INCLUSION_QUERY_ID_HELP = "Include only contacts from companies in this saved query (repeatable)."
|
|
42
|
+
EXCLUSION_QUERY_ID_HELP = "Exclude contacts from companies in this saved query (repeatable)."
|
|
43
|
+
MAX_COMPANIES_HELP = "Maximum number of enriched companies to return; cannot be combined with --max-records."
|
|
44
|
+
RESULTS_BY_COMPANY_HELP = "Maximum contacts per company domain (default 5; 0 removes the cap)."
|
|
45
|
+
INCLUDE_SEARCH_CONTACTS_HELP = "Include contacts from the search index (broader coverage)."
|
|
46
|
+
CONSENSUS_HELP = "Number of query vectors to combine for consensus search."
|
|
28
47
|
|
|
29
48
|
app = typer.Typer(
|
|
30
49
|
help="Find contacts: search and count by filters, look up or match individuals, and run bulk/generative discovery jobs."
|
|
@@ -42,20 +61,43 @@ def search_command(
|
|
|
42
61
|
negate_department: list[str] | None = typer.Option(None, help="Exclude departments (repeatable)."),
|
|
43
62
|
title: list[str] | None = typer.Option(None, help="Filter by job title (repeatable)."),
|
|
44
63
|
negate_title: list[str] | None = typer.Option(None, help="Exclude job titles (repeatable)."),
|
|
64
|
+
name: str | None = typer.Option(None, help=NAME_HELP),
|
|
65
|
+
summary: str | None = typer.Option(None, help=SUMMARY_HELP),
|
|
66
|
+
negate_summary: str | None = typer.Option(None, help=NEGATE_SUMMARY_HELP),
|
|
67
|
+
skills: list[str] | None = typer.Option(None, help=SKILLS_HELP),
|
|
45
68
|
domain: list[str] | None = typer.Option(None, help="Filter by company domain (repeatable)."),
|
|
46
69
|
person_country: list[str] | None = typer.Option(None, help="Filter by contact country (repeatable)."),
|
|
47
70
|
negate_person_country: list[str] | None = typer.Option(None, help="Exclude contact countries (repeatable)."),
|
|
71
|
+
person_state: list[str] | None = typer.Option(None, help=PERSON_STATE_HELP),
|
|
72
|
+
persona_id: list[int] | None = typer.Option(None, help=PERSONA_ID_HELP),
|
|
48
73
|
filter_industry: list[str] | None = typer.Option(None, help="Filter by company industry (repeatable)."),
|
|
49
74
|
negate_filter_industry: list[str] | None = typer.Option(None, help="Exclude company industries (repeatable)."),
|
|
50
75
|
filter_country: list[str] | None = typer.Option(None, help="Filter by company country (repeatable)."),
|
|
51
76
|
negate_filter_country: list[str] | None = typer.Option(None, help="Exclude company countries (repeatable)."),
|
|
77
|
+
filter_state: list[str] | None = typer.Option(None, help=FILTER_STATE_HELP),
|
|
78
|
+
negate_filter_state: list[str] | None = typer.Option(None, help=NEGATE_FILTER_STATE_HELP),
|
|
52
79
|
employee_range: str | None = typer.Option(None, help="Company employee range, e.g. 50-200."),
|
|
53
80
|
has_email: bool | None = typer.Option(
|
|
54
81
|
None, "--has-email/--no-has-email", help="Only contacts with (or without) an email address."
|
|
55
82
|
),
|
|
83
|
+
email_validated: bool | None = typer.Option(
|
|
84
|
+
None, "--email-validated/--no-email-validated", help=EMAIL_VALIDATED_HELP
|
|
85
|
+
),
|
|
86
|
+
has_phone: bool | None = typer.Option(None, "--has-phone/--no-has-phone", help=HAS_PHONE_HELP),
|
|
87
|
+
has_mobile: bool | None = typer.Option(None, "--has-mobile/--no-has-mobile", help=HAS_MOBILE_HELP),
|
|
88
|
+
has_linkedin: bool | None = typer.Option(None, "--has-linkedin/--no-has-linkedin", help=HAS_LINKEDIN_HELP),
|
|
89
|
+
min_connections: int | None = typer.Option(None, help=MIN_CONNECTIONS_HELP),
|
|
56
90
|
jobstart_date: str | None = typer.Option(None, "--jobstart-date", help=JOBSTART_DATE_HELP),
|
|
91
|
+
inclusion_query_id: list[str] | None = typer.Option(None, help=INCLUSION_QUERY_ID_HELP),
|
|
92
|
+
exclusion_query_id: list[str] | None = typer.Option(None, help=EXCLUSION_QUERY_ID_HELP),
|
|
57
93
|
max_records: int | None = typer.Option(None, help="Maximum number of contacts to return."),
|
|
94
|
+
max_companies: int | None = typer.Option(None, help=MAX_COMPANIES_HELP),
|
|
58
95
|
offset: int | None = typer.Option(None, help="Number of records to skip for pagination."),
|
|
96
|
+
results_by_company: int | None = typer.Option(None, help=RESULTS_BY_COMPANY_HELP),
|
|
97
|
+
include_search_contacts: bool | None = typer.Option(
|
|
98
|
+
None, "--include-search-contacts/--no-include-search-contacts", help=INCLUDE_SEARCH_CONTACTS_HELP
|
|
99
|
+
),
|
|
100
|
+
consensus: int | None = typer.Option(None, help=CONSENSUS_HELP),
|
|
59
101
|
fmt: str | None = typer.Option(None, "--format", help=FORMAT_HELP),
|
|
60
102
|
param: list[str] | None = typer.Option(None, "--param", help=PARAM_HELP),
|
|
61
103
|
) -> None:
|
|
@@ -71,18 +113,37 @@ def search_command(
|
|
|
71
113
|
negate_department=negate_department,
|
|
72
114
|
title=title,
|
|
73
115
|
negate_title=negate_title,
|
|
116
|
+
name=name,
|
|
117
|
+
summary=summary,
|
|
118
|
+
negate_summary=negate_summary,
|
|
119
|
+
skills=skills,
|
|
74
120
|
domain=domain,
|
|
75
121
|
person_country=person_country,
|
|
76
122
|
negate_person_country=negate_person_country,
|
|
123
|
+
person_state=person_state,
|
|
124
|
+
persona_id=persona_id,
|
|
77
125
|
filter_industry=filter_industry,
|
|
78
126
|
negate_filter_industry=negate_filter_industry,
|
|
79
127
|
filter_country=filter_country,
|
|
80
128
|
negate_filter_country=negate_filter_country,
|
|
129
|
+
filter_state=filter_state,
|
|
130
|
+
negate_filter_state=negate_filter_state,
|
|
81
131
|
employee_range=employee_range,
|
|
82
132
|
has_email=has_email,
|
|
133
|
+
email_validated=email_validated,
|
|
134
|
+
has_phone=has_phone,
|
|
135
|
+
has_mobile=has_mobile,
|
|
136
|
+
has_linkedin=has_linkedin,
|
|
137
|
+
min_connections=min_connections,
|
|
83
138
|
jobstart_date=jobstart_date,
|
|
139
|
+
inclusion_query_id=inclusion_query_id,
|
|
140
|
+
exclusion_query_id=exclusion_query_id,
|
|
84
141
|
max_records=max_records,
|
|
142
|
+
max_companies=max_companies,
|
|
85
143
|
offset=offset,
|
|
144
|
+
results_by_company=results_by_company,
|
|
145
|
+
include_search_contacts=include_search_contacts,
|
|
146
|
+
consensus=consensus,
|
|
86
147
|
)
|
|
87
148
|
emit(get_client(ctx).contacts.search(build_request(ContactsSearchParams, kwargs)), fmt=fmt)
|
|
88
149
|
|
|
@@ -98,18 +159,43 @@ def count_command(
|
|
|
98
159
|
negate_department: list[str] | None = typer.Option(None, help="Exclude departments (repeatable)."),
|
|
99
160
|
title: list[str] | None = typer.Option(None, help="Filter by job title (repeatable)."),
|
|
100
161
|
negate_title: list[str] | None = typer.Option(None, help="Exclude job titles (repeatable)."),
|
|
162
|
+
name: str | None = typer.Option(None, help=NAME_HELP),
|
|
163
|
+
summary: str | None = typer.Option(None, help=SUMMARY_HELP),
|
|
164
|
+
negate_summary: str | None = typer.Option(None, help=NEGATE_SUMMARY_HELP),
|
|
165
|
+
skills: list[str] | None = typer.Option(None, help=SKILLS_HELP),
|
|
101
166
|
domain: list[str] | None = typer.Option(None, help="Filter by company domain (repeatable)."),
|
|
102
167
|
person_country: list[str] | None = typer.Option(None, help="Filter by contact country (repeatable)."),
|
|
103
168
|
negate_person_country: list[str] | None = typer.Option(None, help="Exclude contact countries (repeatable)."),
|
|
169
|
+
person_state: list[str] | None = typer.Option(None, help=PERSON_STATE_HELP),
|
|
170
|
+
persona_id: list[int] | None = typer.Option(None, help=PERSONA_ID_HELP),
|
|
104
171
|
filter_industry: list[str] | None = typer.Option(None, help="Filter by company industry (repeatable)."),
|
|
105
172
|
negate_filter_industry: list[str] | None = typer.Option(None, help="Exclude company industries (repeatable)."),
|
|
106
173
|
filter_country: list[str] | None = typer.Option(None, help="Filter by company country (repeatable)."),
|
|
107
174
|
negate_filter_country: list[str] | None = typer.Option(None, help="Exclude company countries (repeatable)."),
|
|
175
|
+
filter_state: list[str] | None = typer.Option(None, help=FILTER_STATE_HELP),
|
|
176
|
+
negate_filter_state: list[str] | None = typer.Option(None, help=NEGATE_FILTER_STATE_HELP),
|
|
108
177
|
employee_range: str | None = typer.Option(None, help="Company employee range, e.g. 50-200."),
|
|
109
178
|
has_email: bool | None = typer.Option(
|
|
110
179
|
None, "--has-email/--no-has-email", help="Only contacts with (or without) an email address."
|
|
111
180
|
),
|
|
181
|
+
email_validated: bool | None = typer.Option(
|
|
182
|
+
None, "--email-validated/--no-email-validated", help=EMAIL_VALIDATED_HELP
|
|
183
|
+
),
|
|
184
|
+
has_phone: bool | None = typer.Option(None, "--has-phone/--no-has-phone", help=HAS_PHONE_HELP),
|
|
185
|
+
has_mobile: bool | None = typer.Option(None, "--has-mobile/--no-has-mobile", help=HAS_MOBILE_HELP),
|
|
186
|
+
has_linkedin: bool | None = typer.Option(None, "--has-linkedin/--no-has-linkedin", help=HAS_LINKEDIN_HELP),
|
|
187
|
+
min_connections: int | None = typer.Option(None, help=MIN_CONNECTIONS_HELP),
|
|
112
188
|
jobstart_date: str | None = typer.Option(None, "--jobstart-date", help=JOBSTART_DATE_HELP),
|
|
189
|
+
inclusion_query_id: list[str] | None = typer.Option(None, help=INCLUSION_QUERY_ID_HELP),
|
|
190
|
+
exclusion_query_id: list[str] | None = typer.Option(None, help=EXCLUSION_QUERY_ID_HELP),
|
|
191
|
+
max_records: int | None = typer.Option(None, help="Maximum number of contacts to count (20-10000)."),
|
|
192
|
+
max_companies: int | None = typer.Option(None, help=MAX_COMPANIES_HELP),
|
|
193
|
+
offset: int | None = typer.Option(None, help="Number of records to skip for pagination."),
|
|
194
|
+
results_by_company: int | None = typer.Option(None, help=RESULTS_BY_COMPANY_HELP),
|
|
195
|
+
include_search_contacts: bool | None = typer.Option(
|
|
196
|
+
None, "--include-search-contacts/--no-include-search-contacts", help=INCLUDE_SEARCH_CONTACTS_HELP
|
|
197
|
+
),
|
|
198
|
+
consensus: int | None = typer.Option(None, help=CONSENSUS_HELP),
|
|
113
199
|
fmt: str | None = typer.Option(None, "--format", help=FORMAT_HELP),
|
|
114
200
|
param: list[str] | None = typer.Option(None, "--param", help=PARAM_HELP),
|
|
115
201
|
) -> None:
|
|
@@ -125,16 +211,37 @@ def count_command(
|
|
|
125
211
|
negate_department=negate_department,
|
|
126
212
|
title=title,
|
|
127
213
|
negate_title=negate_title,
|
|
214
|
+
name=name,
|
|
215
|
+
summary=summary,
|
|
216
|
+
negate_summary=negate_summary,
|
|
217
|
+
skills=skills,
|
|
128
218
|
domain=domain,
|
|
129
219
|
person_country=person_country,
|
|
130
220
|
negate_person_country=negate_person_country,
|
|
221
|
+
person_state=person_state,
|
|
222
|
+
persona_id=persona_id,
|
|
131
223
|
filter_industry=filter_industry,
|
|
132
224
|
negate_filter_industry=negate_filter_industry,
|
|
133
225
|
filter_country=filter_country,
|
|
134
226
|
negate_filter_country=negate_filter_country,
|
|
227
|
+
filter_state=filter_state,
|
|
228
|
+
negate_filter_state=negate_filter_state,
|
|
135
229
|
employee_range=employee_range,
|
|
136
230
|
has_email=has_email,
|
|
231
|
+
email_validated=email_validated,
|
|
232
|
+
has_phone=has_phone,
|
|
233
|
+
has_mobile=has_mobile,
|
|
234
|
+
has_linkedin=has_linkedin,
|
|
235
|
+
min_connections=min_connections,
|
|
137
236
|
jobstart_date=jobstart_date,
|
|
237
|
+
inclusion_query_id=inclusion_query_id,
|
|
238
|
+
exclusion_query_id=exclusion_query_id,
|
|
239
|
+
max_records=max_records,
|
|
240
|
+
max_companies=max_companies,
|
|
241
|
+
offset=offset,
|
|
242
|
+
results_by_company=results_by_company,
|
|
243
|
+
include_search_contacts=include_search_contacts,
|
|
244
|
+
consensus=consensus,
|
|
138
245
|
)
|
|
139
246
|
emit(get_client(ctx).contacts.count(build_request(ContactsCountParams, kwargs)), fmt=fmt)
|
|
140
247
|
|
|
@@ -220,19 +327,37 @@ def discover_command(
|
|
|
220
327
|
negate_department: list[str] | None = typer.Option(None, help="Exclude departments (repeatable)."),
|
|
221
328
|
title: list[str] | None = typer.Option(None, help="Filter by job title (repeatable)."),
|
|
222
329
|
negate_title: list[str] | None = typer.Option(None, help="Exclude job titles (repeatable)."),
|
|
330
|
+
name: str | None = typer.Option(None, help=NAME_HELP),
|
|
331
|
+
summary: str | None = typer.Option(None, help=SUMMARY_HELP),
|
|
332
|
+
negate_summary: str | None = typer.Option(None, help=NEGATE_SUMMARY_HELP),
|
|
333
|
+
skills: list[str] | None = typer.Option(None, help=SKILLS_HELP),
|
|
223
334
|
domain: list[str] | None = typer.Option(None, help="Filter by company domain (repeatable)."),
|
|
224
335
|
person_country: list[str] | None = typer.Option(None, help="Filter by contact country (repeatable)."),
|
|
225
336
|
negate_person_country: list[str] | None = typer.Option(None, help="Exclude contact countries (repeatable)."),
|
|
337
|
+
person_state: list[str] | None = typer.Option(None, help=PERSON_STATE_HELP),
|
|
338
|
+
persona_id: list[int] | None = typer.Option(None, help=PERSONA_ID_HELP),
|
|
226
339
|
filter_industry: list[str] | None = typer.Option(None, help="Filter by company industry (repeatable)."),
|
|
227
340
|
negate_filter_industry: list[str] | None = typer.Option(None, help="Exclude company industries (repeatable)."),
|
|
228
341
|
filter_country: list[str] | None = typer.Option(None, help="Filter by company country (repeatable)."),
|
|
229
342
|
negate_filter_country: list[str] | None = typer.Option(None, help="Exclude company countries (repeatable)."),
|
|
343
|
+
filter_state: list[str] | None = typer.Option(None, help=FILTER_STATE_HELP),
|
|
344
|
+
negate_filter_state: list[str] | None = typer.Option(None, help=NEGATE_FILTER_STATE_HELP),
|
|
230
345
|
employee_range: str | None = typer.Option(None, help="Company employee range, e.g. 50-200."),
|
|
231
346
|
has_email: bool | None = typer.Option(
|
|
232
347
|
None, "--has-email/--no-has-email", help="Only contacts with (or without) an email address."
|
|
233
348
|
),
|
|
349
|
+
email_validated: bool | None = typer.Option(
|
|
350
|
+
None, "--email-validated/--no-email-validated", help=EMAIL_VALIDATED_HELP
|
|
351
|
+
),
|
|
352
|
+
has_phone: bool | None = typer.Option(None, "--has-phone/--no-has-phone", help=HAS_PHONE_HELP),
|
|
353
|
+
has_mobile: bool | None = typer.Option(None, "--has-mobile/--no-has-mobile", help=HAS_MOBILE_HELP),
|
|
354
|
+
has_linkedin: bool | None = typer.Option(None, "--has-linkedin/--no-has-linkedin", help=HAS_LINKEDIN_HELP),
|
|
355
|
+
min_connections: int | None = typer.Option(None, help=MIN_CONNECTIONS_HELP),
|
|
234
356
|
jobstart_date: str | None = typer.Option(None, "--jobstart-date", help=JOBSTART_DATE_HELP),
|
|
357
|
+
inclusion_query_id: list[str] | None = typer.Option(None, help=INCLUSION_QUERY_ID_HELP),
|
|
358
|
+
exclusion_query_id: list[str] | None = typer.Option(None, help=EXCLUSION_QUERY_ID_HELP),
|
|
235
359
|
max_records: int | None = typer.Option(None, help="Maximum number of contacts to return."),
|
|
360
|
+
max_companies: int | None = typer.Option(None, help=MAX_COMPANIES_HELP),
|
|
236
361
|
offset: int | None = typer.Option(None, help="Number of records to skip for pagination."),
|
|
237
362
|
results_by_company: int | None = typer.Option(
|
|
238
363
|
None, "--results-by-company", help="Maximum contacts returned per company."
|
|
@@ -258,17 +383,33 @@ def discover_command(
|
|
|
258
383
|
negate_department=negate_department,
|
|
259
384
|
title=title,
|
|
260
385
|
negate_title=negate_title,
|
|
386
|
+
name=name,
|
|
387
|
+
summary=summary,
|
|
388
|
+
negate_summary=negate_summary,
|
|
389
|
+
skills=skills,
|
|
261
390
|
domain=domain,
|
|
262
391
|
person_country=person_country,
|
|
263
392
|
negate_person_country=negate_person_country,
|
|
393
|
+
person_state=person_state,
|
|
394
|
+
persona_id=persona_id,
|
|
264
395
|
filter_industry=filter_industry,
|
|
265
396
|
negate_filter_industry=negate_filter_industry,
|
|
266
397
|
filter_country=filter_country,
|
|
267
398
|
negate_filter_country=negate_filter_country,
|
|
399
|
+
filter_state=filter_state,
|
|
400
|
+
negate_filter_state=negate_filter_state,
|
|
268
401
|
employee_range=employee_range,
|
|
269
402
|
has_email=has_email,
|
|
403
|
+
email_validated=email_validated,
|
|
404
|
+
has_phone=has_phone,
|
|
405
|
+
has_mobile=has_mobile,
|
|
406
|
+
has_linkedin=has_linkedin,
|
|
407
|
+
min_connections=min_connections,
|
|
270
408
|
jobstart_date=jobstart_date,
|
|
409
|
+
inclusion_query_id=inclusion_query_id,
|
|
410
|
+
exclusion_query_id=exclusion_query_id,
|
|
271
411
|
max_records=max_records,
|
|
412
|
+
max_companies=max_companies,
|
|
272
413
|
offset=offset,
|
|
273
414
|
results_by_company=results_by_company,
|
|
274
415
|
include_search_contacts=include_search_contacts,
|
|
@@ -283,6 +424,12 @@ def generate_command(
|
|
|
283
424
|
ctx: typer.Context,
|
|
284
425
|
icp_text: str = typer.Option(..., "--icp-text", help="ICP description used to generate contacts."),
|
|
285
426
|
domain: list[str] = typer.Option(..., "--domain", help="Target company domain (repeatable)."),
|
|
427
|
+
full_domain: list[str] | None = typer.Option(
|
|
428
|
+
None, "--full-domain", help="Domain to send as full_domains to the generation job (repeatable)."
|
|
429
|
+
),
|
|
430
|
+
partial_domain: list[str] | None = typer.Option(
|
|
431
|
+
None, "--partial-domain", help="Domain to send as partial_domains to the generation job (repeatable)."
|
|
432
|
+
),
|
|
286
433
|
context_mode: str | None = typer.Option(None, "--context-mode", help="Context mode for generation."),
|
|
287
434
|
integration_id: str | None = typer.Option(None, "--integration-id", help="Integration ID to use for generation."),
|
|
288
435
|
search_provider_id: str | None = typer.Option(
|
|
@@ -310,6 +457,8 @@ def generate_command(
|
|
|
310
457
|
None,
|
|
311
458
|
icp_text=icp_text,
|
|
312
459
|
domains=domain,
|
|
460
|
+
full_domains=full_domain,
|
|
461
|
+
partial_domains=partial_domain,
|
|
313
462
|
context_mode=context_mode,
|
|
314
463
|
integration_id=integration_id,
|
|
315
464
|
search_provider_id=search_provider_id,
|
|
@@ -15,6 +15,19 @@ LIST_VALUE_SEPARATOR = ","
|
|
|
15
15
|
|
|
16
16
|
FORMAT_HELP = "Output format: json or table (table auto-selected on a TTY; falls back to JSON for non-tabular data)."
|
|
17
17
|
PARAM_HELP = "Extra API parameter as KEY=VALUE (comma-separates into a list); see docs.discolike.com"
|
|
18
|
+
SUBDOMAIN_HELP = "Limit results to this subdomain, up to 20, each at least 3 characters (repeatable)."
|
|
19
|
+
NEGATE_SUBDOMAIN_HELP = "Exclude this subdomain, up to 20, each at least 3 characters (repeatable)."
|
|
20
|
+
START_DATE_HELP = "Minimum company start date (YYYY-MM-DD) or range (YYYY-MM-DD,YYYY-MM-DD)."
|
|
21
|
+
REDIRECT_HELP = "Include or exclude domains that redirect to another domain."
|
|
22
|
+
SOCIAL_HELP = (
|
|
23
|
+
"Filter by social platform presence (repeatable): facebook, instagram, linkedin, pinterest, threads, tiktok, "
|
|
24
|
+
"twitter, x, yelp, youtube, googleplay, applestore, amazon, vk, bluesky, xing."
|
|
25
|
+
)
|
|
26
|
+
NEGATE_SOCIAL_HELP = "Exclude companies with this social profile (repeatable); same values as --social."
|
|
27
|
+
LANGUAGE_HELP = "Filter by site language as an ISO 639-1 code, e.g. en, de, pb, zt (repeatable)."
|
|
28
|
+
NEGATE_LANGUAGE_HELP = "Exclude this site language (repeatable); same values as --language."
|
|
29
|
+
EXCLUDE_LEADGEN_HELP = "Exclude suspected lead generation sites (on by default)."
|
|
30
|
+
COUNT_EXCLUDE_LEADGEN_HELP = "Exclude suspected lead generation sites (off by default for count)."
|
|
18
31
|
|
|
19
32
|
|
|
20
33
|
def _parse_param(raw: str) -> tuple[str, str | list[str]]:
|
|
@@ -45,8 +58,15 @@ def discover_command(
|
|
|
45
58
|
negate_country: list[str] | None = typer.Option(None, help="Negate the --country filter (repeatable)."),
|
|
46
59
|
state: list[str] | None = typer.Option(None, help="State or region filter (repeatable)."),
|
|
47
60
|
negate_state: list[str] | None = typer.Option(None, help="Negate the --state filter (repeatable)."),
|
|
61
|
+
subdomain: list[str] | None = typer.Option(None, help=SUBDOMAIN_HELP),
|
|
62
|
+
negate_subdomain: list[str] | None = typer.Option(None, help=NEGATE_SUBDOMAIN_HELP),
|
|
63
|
+
language: list[str] | None = typer.Option(None, help=LANGUAGE_HELP),
|
|
64
|
+
negate_language: list[str] | None = typer.Option(None, help=NEGATE_LANGUAGE_HELP),
|
|
65
|
+
social: list[str] | None = typer.Option(None, help=SOCIAL_HELP),
|
|
66
|
+
negate_social: list[str] | None = typer.Option(None, help=NEGATE_SOCIAL_HELP),
|
|
48
67
|
employee_range: str | None = typer.Option(None, help="Employee count range filter."),
|
|
49
68
|
revenue_range: str | None = typer.Option(None, help="Revenue range filter."),
|
|
69
|
+
start_date: str | None = typer.Option(None, help=START_DATE_HELP),
|
|
50
70
|
business_model: list[str] | None = typer.Option(None, help="Business model filter (repeatable)."),
|
|
51
71
|
negate_business_model: list[str] | None = typer.Option(
|
|
52
72
|
None, help="Negate the --business-model filter (repeatable)."
|
|
@@ -55,7 +75,38 @@ def discover_command(
|
|
|
55
75
|
negate_tech_stack: list[str] | None = typer.Option(None, help="Negate the --tech-stack filter (repeatable)."),
|
|
56
76
|
min_digital_footprint: int | None = typer.Option(None, help="Minimum digital footprint score."),
|
|
57
77
|
max_digital_footprint: int | None = typer.Option(None, help="Maximum digital footprint score."),
|
|
78
|
+
min_similarity: int | None = typer.Option(None, help="Minimum similarity score to include (0-99)."),
|
|
79
|
+
variance: str | None = typer.Option(
|
|
80
|
+
None, help="Result diversity control: LOW, MID_LOW, MEDIUM, MID_HIGH, HIGH, UNRESTRICTED."
|
|
81
|
+
),
|
|
82
|
+
consensus: int | None = typer.Option(
|
|
83
|
+
None, help="Number of top results for the consensus search vector (1-20); higher reduces specificity."
|
|
84
|
+
),
|
|
85
|
+
redirect: bool | None = typer.Option(None, "--redirect/--no-redirect", help=REDIRECT_HELP),
|
|
86
|
+
exclude_leadgen: bool | None = typer.Option(
|
|
87
|
+
None, "--exclude-leadgen/--no-exclude-leadgen", help=EXCLUDE_LEADGEN_HELP
|
|
88
|
+
),
|
|
89
|
+
retrieval: bool | None = typer.Option(
|
|
90
|
+
None, "--retrieval/--no-retrieval", help="Enable page data retrieval using the Extract API."
|
|
91
|
+
),
|
|
92
|
+
enhanced: bool | None = typer.Option(
|
|
93
|
+
None, "--enhanced/--no-enhanced", help="Enable AI-powered result enhancement for improved relevance."
|
|
94
|
+
),
|
|
95
|
+
include_search_domains: bool | None = typer.Option(
|
|
96
|
+
None,
|
|
97
|
+
"--include-search-domains/--no-include-search-domains",
|
|
98
|
+
help="Include the input domains in results (excluded by default).",
|
|
99
|
+
),
|
|
100
|
+
auto_icp_text: bool | None = typer.Option(
|
|
101
|
+
None, "--auto-icp-text/--no-auto-icp-text", help="Auto-generate ICP text from the provided domain(s)."
|
|
102
|
+
),
|
|
103
|
+
auto_phrase_match: bool | None = typer.Option(
|
|
104
|
+
None, "--auto-phrase-match/--no-auto-phrase-match", help="Auto-generate phrase matches from ICP text."
|
|
105
|
+
),
|
|
58
106
|
exclude_domain: list[str] | None = typer.Option(None, help="Domain to exclude from results (repeatable)."),
|
|
107
|
+
inclusion_query_id: list[str] | None = typer.Option(
|
|
108
|
+
None, help="Saved query ID whose domains are included (repeatable); requires the STARTER plan."
|
|
109
|
+
),
|
|
59
110
|
exclusion_query_id: list[str] | None = typer.Option(
|
|
60
111
|
None, help="Saved query ID whose results are excluded (repeatable)."
|
|
61
112
|
),
|
|
@@ -81,15 +132,33 @@ def discover_command(
|
|
|
81
132
|
negate_country=negate_country,
|
|
82
133
|
state=state,
|
|
83
134
|
negate_state=negate_state,
|
|
135
|
+
subdomain=subdomain,
|
|
136
|
+
negate_subdomain=negate_subdomain,
|
|
137
|
+
language=language,
|
|
138
|
+
negate_language=negate_language,
|
|
139
|
+
social=social,
|
|
140
|
+
negate_social=negate_social,
|
|
84
141
|
employee_range=employee_range,
|
|
85
142
|
revenue_range=revenue_range,
|
|
143
|
+
start_date=start_date,
|
|
86
144
|
business_model=business_model,
|
|
87
145
|
negate_business_model=negate_business_model,
|
|
88
146
|
tech_stack=tech_stack,
|
|
89
147
|
negate_tech_stack=negate_tech_stack,
|
|
90
148
|
min_digital_footprint=min_digital_footprint,
|
|
91
149
|
max_digital_footprint=max_digital_footprint,
|
|
150
|
+
min_similarity=min_similarity,
|
|
151
|
+
variance=variance,
|
|
152
|
+
consensus=consensus,
|
|
153
|
+
redirect=redirect,
|
|
154
|
+
exclude_leadgen=exclude_leadgen,
|
|
155
|
+
retrieval=retrieval,
|
|
156
|
+
enhanced=enhanced,
|
|
157
|
+
include_search_domains=include_search_domains,
|
|
158
|
+
auto_icp_text=auto_icp_text,
|
|
159
|
+
auto_phrase_match=auto_phrase_match,
|
|
92
160
|
exclude_domain=exclude_domain,
|
|
161
|
+
inclusion_query_id=inclusion_query_id,
|
|
93
162
|
exclusion_query_id=exclusion_query_id,
|
|
94
163
|
max_records=max_records,
|
|
95
164
|
offset=offset,
|
|
@@ -109,8 +178,15 @@ def count_command(
|
|
|
109
178
|
negate_country: list[str] | None = typer.Option(None, help="Negate the --country filter (repeatable)."),
|
|
110
179
|
state: list[str] | None = typer.Option(None, help="State or region filter (repeatable)."),
|
|
111
180
|
negate_state: list[str] | None = typer.Option(None, help="Negate the --state filter (repeatable)."),
|
|
181
|
+
subdomain: list[str] | None = typer.Option(None, help=SUBDOMAIN_HELP),
|
|
182
|
+
negate_subdomain: list[str] | None = typer.Option(None, help=NEGATE_SUBDOMAIN_HELP),
|
|
183
|
+
language: list[str] | None = typer.Option(None, help=LANGUAGE_HELP),
|
|
184
|
+
negate_language: list[str] | None = typer.Option(None, help=NEGATE_LANGUAGE_HELP),
|
|
185
|
+
social: list[str] | None = typer.Option(None, help=SOCIAL_HELP),
|
|
186
|
+
negate_social: list[str] | None = typer.Option(None, help=NEGATE_SOCIAL_HELP),
|
|
112
187
|
employee_range: str | None = typer.Option(None, help="Employee count range filter."),
|
|
113
188
|
revenue_range: str | None = typer.Option(None, help="Revenue range filter."),
|
|
189
|
+
start_date: str | None = typer.Option(None, help=START_DATE_HELP),
|
|
114
190
|
business_model: list[str] | None = typer.Option(None, help="Business model filter (repeatable)."),
|
|
115
191
|
negate_business_model: list[str] | None = typer.Option(
|
|
116
192
|
None, help="Negate the --business-model filter (repeatable)."
|
|
@@ -119,6 +195,10 @@ def count_command(
|
|
|
119
195
|
negate_tech_stack: list[str] | None = typer.Option(None, help="Negate the --tech-stack filter (repeatable)."),
|
|
120
196
|
min_digital_footprint: int | None = typer.Option(None, help="Minimum digital footprint score."),
|
|
121
197
|
max_digital_footprint: int | None = typer.Option(None, help="Maximum digital footprint score."),
|
|
198
|
+
redirect: bool | None = typer.Option(None, "--redirect/--no-redirect", help=REDIRECT_HELP),
|
|
199
|
+
exclude_leadgen: bool | None = typer.Option(
|
|
200
|
+
None, "--exclude-leadgen/--no-exclude-leadgen", help=COUNT_EXCLUDE_LEADGEN_HELP
|
|
201
|
+
),
|
|
122
202
|
fmt: str | None = typer.Option(None, "--format", help=FORMAT_HELP),
|
|
123
203
|
param: list[str] | None = typer.Option(None, "--param", help=PARAM_HELP),
|
|
124
204
|
) -> None:
|
|
@@ -137,14 +217,23 @@ def count_command(
|
|
|
137
217
|
negate_country=negate_country,
|
|
138
218
|
state=state,
|
|
139
219
|
negate_state=negate_state,
|
|
220
|
+
subdomain=subdomain,
|
|
221
|
+
negate_subdomain=negate_subdomain,
|
|
222
|
+
language=language,
|
|
223
|
+
negate_language=negate_language,
|
|
224
|
+
social=social,
|
|
225
|
+
negate_social=negate_social,
|
|
140
226
|
employee_range=employee_range,
|
|
141
227
|
revenue_range=revenue_range,
|
|
228
|
+
start_date=start_date,
|
|
142
229
|
business_model=business_model,
|
|
143
230
|
negate_business_model=negate_business_model,
|
|
144
231
|
tech_stack=tech_stack,
|
|
145
232
|
negate_tech_stack=negate_tech_stack,
|
|
146
233
|
min_digital_footprint=min_digital_footprint,
|
|
147
234
|
max_digital_footprint=max_digital_footprint,
|
|
235
|
+
redirect=redirect,
|
|
236
|
+
exclude_leadgen=exclude_leadgen,
|
|
148
237
|
),
|
|
149
238
|
)
|
|
150
239
|
emit(get_client(ctx).count(request), fmt=fmt)
|
|
@@ -19,6 +19,7 @@ DEFAULT_WAIT_TIMEOUT_SECONDS = 900.0
|
|
|
19
19
|
FORMAT_HELP = "Output format: json or table (table auto-selected on a TTY; falls back to JSON for non-tabular data)."
|
|
20
20
|
WAIT_HELP = "Block until the job finishes, streaming progress to stderr."
|
|
21
21
|
TIMEOUT_HELP = "Max seconds to wait with --wait."
|
|
22
|
+
QUERY_ID_HELP = "Saved query ID whose domains are included alongside the file/--domain ones (repeatable)."
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
@handle_errors
|
|
@@ -76,6 +77,7 @@ def append_command(
|
|
|
76
77
|
file: pathlib.Path = typer.Argument(..., help="CSV of domains to enrich."),
|
|
77
78
|
dataset: list[str] | None = typer.Option(None, "--dataset", help="Dataset to append (repeatable)."),
|
|
78
79
|
domain_column: str | None = typer.Option(None, "--domain-column", help="Column in the CSV that holds domains."),
|
|
80
|
+
query_id: list[str] | None = typer.Option(None, "--query-id", help=QUERY_ID_HELP),
|
|
79
81
|
csv: bool | None = typer.Option(
|
|
80
82
|
None, "--csv/--no-csv", help="Request the enriched rows as CSV (written via --output)."
|
|
81
83
|
),
|
|
@@ -85,7 +87,9 @@ def append_command(
|
|
|
85
87
|
"""Enrich a CSV of domains with DiscoLike datasets."""
|
|
86
88
|
from discolike_cli.main import get_client
|
|
87
89
|
|
|
88
|
-
request = build_request(
|
|
90
|
+
request = build_request(
|
|
91
|
+
AppendParams, _merge_params(None, dataset=dataset, domain_column=domain_column, query_id=query_id, csv=csv)
|
|
92
|
+
)
|
|
89
93
|
result = get_client(ctx).append(request, file=file)
|
|
90
94
|
if isinstance(result, bytes):
|
|
91
95
|
if output is None:
|
|
@@ -102,6 +106,7 @@ def segment_command(
|
|
|
102
106
|
domain: list[str] | None = typer.Option(None, "--domain", help="Domain to segment (repeatable)."),
|
|
103
107
|
file: pathlib.Path | None = typer.Option(None, "--file", help="CSV of domains to segment (instead of --domain)."),
|
|
104
108
|
domain_column: str | None = typer.Option(None, "--domain-column", help="Column in --file that holds domains."),
|
|
109
|
+
query_id: list[str] | None = typer.Option(None, "--query-id", help=QUERY_ID_HELP),
|
|
105
110
|
max_segments: int | None = typer.Option(None, "--max-segments", help="Maximum number of segments to produce."),
|
|
106
111
|
wait: bool = typer.Option(False, "--wait", help=WAIT_HELP),
|
|
107
112
|
timeout: float = typer.Option(DEFAULT_WAIT_TIMEOUT_SECONDS, "--timeout", help=TIMEOUT_HELP),
|
|
@@ -116,11 +121,14 @@ def segment_command(
|
|
|
116
121
|
client = get_client(ctx)
|
|
117
122
|
if file is not None:
|
|
118
123
|
request = build_request(
|
|
119
|
-
SegmentFileParams,
|
|
124
|
+
SegmentFileParams,
|
|
125
|
+
_merge_params(None, domain_column=domain_column, query_id=query_id, max_segments=max_segments),
|
|
120
126
|
)
|
|
121
127
|
job = client.segment_file(request, file=file)
|
|
122
128
|
else:
|
|
123
129
|
assert domain is not None
|
|
124
|
-
request = build_request(
|
|
130
|
+
request = build_request(
|
|
131
|
+
SegmentParams, _merge_params(None, domains=",".join(domain), query_id=query_id, max_segments=max_segments)
|
|
132
|
+
)
|
|
125
133
|
job = client.segment(request)
|
|
126
134
|
run_job(job, wait=wait, timeout=timeout, fmt=fmt)
|
|
@@ -18,6 +18,7 @@ from discolike_cli import enrich
|
|
|
18
18
|
from discolike_cli import match
|
|
19
19
|
from discolike_cli import providers
|
|
20
20
|
from discolike_cli import queries
|
|
21
|
+
from discolike_cli import signup
|
|
21
22
|
|
|
22
23
|
app = typer.Typer(
|
|
23
24
|
name="discolike",
|
|
@@ -70,3 +71,4 @@ app.command(name="extract")(company.extract_command)
|
|
|
70
71
|
app.command(name="validate-icp")(enrich.validate_icp_command)
|
|
71
72
|
app.command(name="append")(enrich.append_command)
|
|
72
73
|
app.command(name="segment")(enrich.segment_command)
|
|
74
|
+
app.command(name="signup")(signup.signup_command)
|