discolike-cli 0.1.0__tar.gz → 0.2.0__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 (49) hide show
  1. discolike_cli-0.2.0/LICENSE +21 -0
  2. {discolike_cli-0.1.0 → discolike_cli-0.2.0}/PKG-INFO +14 -6
  3. {discolike_cli-0.1.0 → discolike_cli-0.2.0}/README.md +3 -3
  4. {discolike_cli-0.1.0 → discolike_cli-0.2.0}/pyproject.toml +12 -2
  5. {discolike_cli-0.1.0 → discolike_cli-0.2.0}/src/discolike_cli/_output.py +26 -8
  6. discolike_cli-0.2.0/src/discolike_cli/account.py +22 -0
  7. discolike_cli-0.2.0/src/discolike_cli/auth.py +81 -0
  8. discolike_cli-0.2.0/src/discolike_cli/company.py +121 -0
  9. discolike_cli-0.2.0/src/discolike_cli/contacts.py +310 -0
  10. discolike_cli-0.2.0/src/discolike_cli/discogen.py +150 -0
  11. discolike_cli-0.2.0/src/discolike_cli/discover.py +144 -0
  12. discolike_cli-0.2.0/src/discolike_cli/email.py +166 -0
  13. discolike_cli-0.2.0/src/discolike_cli/enrich.py +111 -0
  14. {discolike_cli-0.1.0 → discolike_cli-0.2.0}/src/discolike_cli/main.py +18 -5
  15. discolike_cli-0.2.0/src/discolike_cli/match.py +63 -0
  16. discolike_cli-0.2.0/src/discolike_cli/providers.py +246 -0
  17. discolike_cli-0.2.0/src/discolike_cli/py.typed +0 -0
  18. discolike_cli-0.2.0/src/discolike_cli/queries.py +127 -0
  19. discolike_cli-0.2.0/tests/conftest.py +41 -0
  20. discolike_cli-0.2.0/tests/test_account.py +23 -0
  21. discolike_cli-0.2.0/tests/test_auth.py +210 -0
  22. discolike_cli-0.2.0/tests/test_company.py +134 -0
  23. discolike_cli-0.1.0/tests/test_contacts.py → discolike_cli-0.2.0/tests/test_contacts_cli.py +111 -61
  24. discolike_cli-0.1.0/tests/test_discogen.py → discolike_cli-0.2.0/tests/test_discogen_cli.py +51 -55
  25. discolike_cli-0.2.0/tests/test_discover.py +131 -0
  26. discolike_cli-0.2.0/tests/test_email_cli.py +283 -0
  27. discolike_cli-0.1.0/tests/test_enrich.py → discolike_cli-0.2.0/tests/test_enrich_cli.py +60 -64
  28. discolike_cli-0.2.0/tests/test_match_cli.py +219 -0
  29. {discolike_cli-0.1.0 → discolike_cli-0.2.0}/tests/test_output.py +53 -3
  30. discolike_cli-0.2.0/tests/test_providers_cli.py +219 -0
  31. discolike_cli-0.2.0/tests/test_queries_cli.py +180 -0
  32. discolike_cli-0.1.0/src/discolike_cli/account.py +0 -16
  33. discolike_cli-0.1.0/src/discolike_cli/auth.py +0 -60
  34. discolike_cli-0.1.0/src/discolike_cli/company.py +0 -87
  35. discolike_cli-0.1.0/src/discolike_cli/contacts.py +0 -230
  36. discolike_cli-0.1.0/src/discolike_cli/discogen.py +0 -119
  37. discolike_cli-0.1.0/src/discolike_cli/discover.py +0 -111
  38. discolike_cli-0.1.0/src/discolike_cli/enrich.py +0 -91
  39. discolike_cli-0.1.0/src/discolike_cli/match.py +0 -54
  40. discolike_cli-0.1.0/src/discolike_cli/queries.py +0 -65
  41. discolike_cli-0.1.0/tests/conftest.py +0 -23
  42. discolike_cli-0.1.0/tests/test_account.py +0 -29
  43. discolike_cli-0.1.0/tests/test_auth.py +0 -121
  44. discolike_cli-0.1.0/tests/test_company.py +0 -117
  45. discolike_cli-0.1.0/tests/test_discover.py +0 -112
  46. discolike_cli-0.1.0/tests/test_match.py +0 -178
  47. discolike_cli-0.1.0/tests/test_queries_cli.py +0 -102
  48. {discolike_cli-0.1.0 → discolike_cli-0.2.0}/.gitignore +0 -0
  49. {discolike_cli-0.1.0 → discolike_cli-0.2.0}/src/discolike_cli/__init__.py +0 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DiscoLike Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,30 +1,38 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: discolike-cli
3
- Version: 0.1.0
3
+ Version: 0.2.0
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
7
7
  Project-URL: Repository, https://github.com/Discolike/discolike-python
8
+ Project-URL: Changelog, https://github.com/Discolike/discolike-python/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/Discolike/discolike-python/issues
8
10
  Author-email: DiscoLike <support@discolike.com>
9
11
  License-Expression: MIT
12
+ License-File: LICENSE
10
13
  Keywords: business-data,cli,discolike,enrichment
11
14
  Classifier: Development Status :: 4 - Beta
15
+ Classifier: Environment :: Console
12
16
  Classifier: Intended Audience :: Developers
17
+ Classifier: Operating System :: OS Independent
13
18
  Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
14
20
  Classifier: Programming Language :: Python :: 3.10
15
21
  Classifier: Programming Language :: Python :: 3.11
16
22
  Classifier: Programming Language :: Python :: 3.12
17
23
  Classifier: Programming Language :: Python :: 3.13
18
24
  Classifier: Programming Language :: Python :: 3.14
25
+ Classifier: Topic :: Utilities
26
+ Classifier: Typing :: Typed
19
27
  Requires-Python: >=3.10
20
- Requires-Dist: discolike>=0.1.0
28
+ Requires-Dist: discolike==0.2.0
21
29
  Requires-Dist: rich>=13.0
22
30
  Requires-Dist: typer>=0.12
23
31
  Description-Content-Type: text/markdown
24
32
 
25
33
  # discolike-cli
26
34
 
27
- Official CLI for the [DiscoLike API](https://www.discolike.com) — the `discolike` command from your terminal. Built on the [`discolike`](https://pypi.org/project/discolike/) SDK.
35
+ Official CLI for the [DiscoLike API](https://discolike.com) — the `discolike` command from your terminal. Built on the [`discolike`](https://pypi.org/project/discolike/) SDK.
28
36
 
29
37
  ## Installation
30
38
 
@@ -51,7 +59,7 @@ Prompts for an API key (or pass `--api-key`) and verifies it against your accoun
51
59
  ## Quickstart
52
60
 
53
61
  ```bash
54
- discolike discover --icp-text "managed IT services for SMBs" --country US --max-records 25
62
+ discolike discover --icp-prompt "managed IT services for SMBs" --country US --max-records 25
55
63
  discolike match "Stripe Inc" --city "San Francisco"
56
64
  discolike match --file companies.csv --name-column company_name --wait
57
65
  discolike count --phrase-match "book a demo" --country US
@@ -59,7 +67,7 @@ discolike company data stripe.com
59
67
  discolike extract https://stripe.com/enterprise
60
68
  ```
61
69
 
62
- Top-level commands: `discover`, `count`, `match`, `extract`, `validate-icp`, `append`, `segment` — plus `auth`, `company`, `contacts`, `discogen`, `queries`, and `account` command groups.
70
+ Top-level commands: `discover`, `count`, `match`, `extract`, `validate-icp`, `append`, `segment` — plus `auth`, `company`, `contacts`, `discogen`, `queries`, `account`, `search-providers`, and `llm-providers` command groups.
63
71
 
64
72
  ### Conventions
65
73
 
@@ -1,6 +1,6 @@
1
1
  # discolike-cli
2
2
 
3
- Official CLI for the [DiscoLike API](https://www.discolike.com) — the `discolike` command from your terminal. Built on the [`discolike`](https://pypi.org/project/discolike/) SDK.
3
+ Official CLI for the [DiscoLike API](https://discolike.com) — the `discolike` command from your terminal. Built on the [`discolike`](https://pypi.org/project/discolike/) SDK.
4
4
 
5
5
  ## Installation
6
6
 
@@ -27,7 +27,7 @@ Prompts for an API key (or pass `--api-key`) and verifies it against your accoun
27
27
  ## Quickstart
28
28
 
29
29
  ```bash
30
- discolike discover --icp-text "managed IT services for SMBs" --country US --max-records 25
30
+ discolike discover --icp-prompt "managed IT services for SMBs" --country US --max-records 25
31
31
  discolike match "Stripe Inc" --city "San Francisco"
32
32
  discolike match --file companies.csv --name-column company_name --wait
33
33
  discolike count --phrase-match "book a demo" --country US
@@ -35,7 +35,7 @@ discolike company data stripe.com
35
35
  discolike extract https://stripe.com/enterprise
36
36
  ```
37
37
 
38
- Top-level commands: `discover`, `count`, `match`, `extract`, `validate-icp`, `append`, `segment` — plus `auth`, `company`, `contacts`, `discogen`, `queries`, and `account` command groups.
38
+ Top-level commands: `discover`, `count`, `match`, `extract`, `validate-icp`, `append`, `segment` — plus `auth`, `company`, `contacts`, `discogen`, `queries`, `account`, `search-providers`, and `llm-providers` command groups.
39
39
 
40
40
  ### Conventions
41
41
 
@@ -4,14 +4,15 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "discolike-cli"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Official CLI for the DiscoLike API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
+ license-files = ["LICENSE"]
11
12
  requires-python = ">=3.10"
12
13
  authors = [{ name = "DiscoLike", email = "support@discolike.com" }]
13
14
  dependencies = [
14
- "discolike>=0.1.0",
15
+ "discolike==0.2.0",
15
16
  "typer>=0.12",
16
17
  "rich>=13.0",
17
18
  ]
@@ -25,6 +26,11 @@ classifiers = [
25
26
  "Programming Language :: Python :: 3.12",
26
27
  "Programming Language :: Python :: 3.13",
27
28
  "Programming Language :: Python :: 3.14",
29
+ "Programming Language :: Python :: 3 :: Only",
30
+ "Operating System :: OS Independent",
31
+ "Environment :: Console",
32
+ "Topic :: Utilities",
33
+ "Typing :: Typed",
28
34
  ]
29
35
 
30
36
  [project.scripts]
@@ -34,12 +40,16 @@ discolike = "discolike_cli.main:app"
34
40
  Homepage = "https://www.discolike.com"
35
41
  Documentation = "https://docs.discolike.com"
36
42
  Repository = "https://github.com/Discolike/discolike-python"
43
+ Changelog = "https://github.com/Discolike/discolike-python/blob/main/CHANGELOG.md"
44
+ Issues = "https://github.com/Discolike/discolike-python/issues"
37
45
 
38
46
  [tool.uv.sources]
47
+ discolike-testkit = { workspace = true }
39
48
  discolike = { workspace = true }
40
49
 
41
50
  [dependency-groups]
42
51
  dev = [
52
+ "discolike-testkit",
43
53
  "pytest>=8",
44
54
  "pytest-asyncio>=0.24",
45
55
  "ruff>=0.8",
@@ -60,21 +60,39 @@ def _normalize(data: Any) -> Any: # noqa: ANN401 -- accepts arbitrary JSON-seri
60
60
  return data
61
61
 
62
62
 
63
- def _is_flat_dict(value: Any) -> bool: # noqa: ANN401 -- runtime-narrowed via isinstance, not a fixed shape
64
- return isinstance(value, dict) and not any(isinstance(v, (dict, list)) for v in value.values())
63
+ TABLE_CELL_MAX_CHARS = 80
64
+ TABLE_MAX_COLUMNS = 8
65
+ _SCALAR_TYPES = (str, int, float, bool, type(None))
66
+
67
+
68
+ def _scalar_columns(rows: list[dict[str, Any]]) -> list[str]:
69
+ columns = [column for column in rows[0] if all(isinstance(row.get(column), _SCALAR_TYPES) for row in rows)]
70
+ return columns[:TABLE_MAX_COLUMNS]
65
71
 
66
72
 
67
73
  def _qualifies_for_table(data: Any) -> bool: # noqa: ANN401 -- runtime-narrowed via isinstance, not a fixed shape
68
- return isinstance(data, list) and len(data) > 0 and all(_is_flat_dict(item) for item in data)
74
+ return (
75
+ isinstance(data, list)
76
+ and len(data) > 0
77
+ and all(isinstance(item, dict) for item in data)
78
+ and len(_scalar_columns(data)) > 0
79
+ )
80
+
81
+
82
+ def _cell(value: Any) -> str: # noqa: ANN401 -- scalar JSON value, narrowed by _scalar_columns
83
+ text = "" if value is None else str(value)
84
+ if len(text) > TABLE_CELL_MAX_CHARS:
85
+ return text[: TABLE_CELL_MAX_CHARS - 1] + "…"
86
+ return text
69
87
 
70
88
 
71
89
  def _render_table(rows: list[dict[str, Any]]) -> None:
72
90
  table = Table()
73
- columns = list(rows[0].keys())
91
+ columns = _scalar_columns(rows)
74
92
  for column in columns:
75
- table.add_column(column)
93
+ table.add_column(column, overflow="ellipsis", no_wrap=True)
76
94
  for row in rows:
77
- table.add_row(*(str(row.get(column, "")) for column in columns))
95
+ table.add_row(*(_cell(row.get(column)) for column in columns))
78
96
  Console().print(table)
79
97
 
80
98
 
@@ -117,7 +135,7 @@ def handle_errors(fn: Callable[P, R]) -> Callable[P, R]:
117
135
  return wrapper
118
136
 
119
137
 
120
- def run_job(job: SupportsWait, *, wait: bool, timeout: float) -> None:
138
+ def run_job(job: SupportsWait, *, wait: bool, timeout: float, fmt: str | None = None) -> None:
121
139
  if not wait:
122
140
  emit(
123
141
  {
@@ -132,4 +150,4 @@ def run_job(job: SupportsWait, *, wait: bool, timeout: float) -> None:
132
150
  sys.stderr.write(f"progress: {status.progress}%\n")
133
151
 
134
152
  final = job.wait(timeout=timeout, on_poll=_on_poll)
135
- emit(final.results if final.results is not None else final.to_dict())
153
+ emit(final.results if final.results is not None else final.to_dict(), fmt=fmt)
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+ from discolike_cli._output import emit
6
+ from discolike_cli._output import handle_errors
7
+
8
+ FORMAT_HELP = "Output format: json or table (table auto-selected on a TTY; falls back to JSON for non-tabular data)."
9
+
10
+ app = typer.Typer(help="Account usage and quota.")
11
+
12
+
13
+ @app.command("usage")
14
+ @handle_errors
15
+ def usage_command(
16
+ ctx: typer.Context,
17
+ fmt: str | None = typer.Option(None, "--format", help=FORMAT_HELP),
18
+ ) -> None:
19
+ """Month-to-date usage: requests, records, and spend."""
20
+ from discolike_cli.main import get_client
21
+
22
+ emit(get_client(ctx).account.usage(), fmt=fmt)
@@ -0,0 +1,81 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sys
5
+ from typing import Any
6
+
7
+ import typer
8
+
9
+ from discolike._config import KEYS_URL
10
+ from discolike._config import delete_config
11
+ from discolike._config import load_config
12
+ from discolike._config import resolve_api_key
13
+ from discolike._config import save_config
14
+ from discolike_cli._output import emit
15
+ from discolike_cli._output import handle_errors
16
+
17
+ app = typer.Typer(help="Manage API credentials: log in, check key status, log out.")
18
+
19
+ MASKED_VISIBLE_CHARS = 4
20
+
21
+ SOURCE_OPTION = "option"
22
+ SOURCE_ENV = "env"
23
+ SOURCE_CONFIG = "config"
24
+
25
+
26
+ def _mask(key: str) -> str:
27
+ return "…" + key[-MASKED_VISIBLE_CHARS:]
28
+
29
+
30
+ def _global_key_source(ctx: typer.Context) -> str:
31
+ # typer vendors click without re-exporting ParameterSource, so match on the enum member name.
32
+ source = ctx.find_root().get_parameter_source("api_key")
33
+ return SOURCE_ENV if source is not None and source.name == "ENVIRONMENT" else SOURCE_OPTION
34
+
35
+
36
+ def _verify(ctx: typer.Context, *, api_key: str) -> None:
37
+ from discolike_cli.main import build_client
38
+
39
+ kwargs: dict[str, Any] = {"api_key": api_key}
40
+ base_url = ctx.obj.get("base_url")
41
+ if base_url is not None:
42
+ kwargs["base_url"] = base_url
43
+ build_client(**kwargs).account.usage()
44
+
45
+
46
+ @app.command()
47
+ @handle_errors
48
+ def login(
49
+ ctx: typer.Context,
50
+ api_key: str | None = typer.Option(None, help=f"API key. Create one at {KEYS_URL}. Prompted for if omitted."),
51
+ ) -> None:
52
+ """Verify an API key and save it to the local config file."""
53
+ # An ambient DISCOLIKE_API_KEY must not silently become the saved key; only an explicit flag may.
54
+ passed_globally = ctx.obj.get("api_key") if _global_key_source(ctx) == SOURCE_OPTION else None
55
+ key = api_key or passed_globally or typer.prompt("API key", hide_input=True)
56
+ _verify(ctx, api_key=key)
57
+ save_config({"auth_method": "api_key", "api_key": key})
58
+ print(json.dumps({"logged_in": True, "source": "api_key"}), file=sys.stderr)
59
+
60
+
61
+ @app.command()
62
+ @handle_errors
63
+ def status(ctx: typer.Context) -> None:
64
+ """Show which API key is in use (option, env, or config) and verify it against the API."""
65
+ key = ctx.obj.get("api_key")
66
+ source = _global_key_source(ctx)
67
+ if not key:
68
+ key = load_config().get("api_key")
69
+ source = SOURCE_CONFIG
70
+ if not key:
71
+ resolve_api_key(None)
72
+ _verify(ctx, api_key=str(key))
73
+ emit({"source": source, "api_key": _mask(str(key)), "valid": True})
74
+
75
+
76
+ @app.command()
77
+ @handle_errors
78
+ def logout() -> None:
79
+ """Delete saved credentials from the local config file."""
80
+ delete_config()
81
+ emit({"logged_out": True})
@@ -0,0 +1,121 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+ from discolike_cli._output import emit
6
+ from discolike_cli._output import handle_errors
7
+
8
+ FORMAT_HELP = "Output format: json or table (table auto-selected on a TTY; falls back to JSON for non-tabular data)."
9
+ DOMAIN_HELP = "Company domain, e.g. stripe.com"
10
+ MATCH_HELP = "Domain match mode, e.g. loose"
11
+
12
+ app = typer.Typer(
13
+ help="Company profiles by domain: firmographics, scores, growth, redirects, vendors, subsidiaries, and public links."
14
+ )
15
+
16
+
17
+ @app.command()
18
+ @handle_errors
19
+ def data(
20
+ ctx: typer.Context,
21
+ domain: str = typer.Argument(..., help=DOMAIN_HELP),
22
+ fmt: str | None = typer.Option(None, "--format", help=FORMAT_HELP),
23
+ ) -> None:
24
+ """Full company profile (firmographics) for a domain."""
25
+ from discolike_cli.main import get_client
26
+
27
+ emit(get_client(ctx).companies.data(domain=domain), fmt=fmt)
28
+
29
+
30
+ @app.command()
31
+ @handle_errors
32
+ def score(
33
+ ctx: typer.Context,
34
+ domain: str = typer.Argument(..., help=DOMAIN_HELP),
35
+ fmt: str | None = typer.Option(None, "--format", help=FORMAT_HELP),
36
+ ) -> None:
37
+ """Company score for a domain."""
38
+ from discolike_cli.main import get_client
39
+
40
+ emit(get_client(ctx).companies.score(domain=domain), fmt=fmt)
41
+
42
+
43
+ @app.command()
44
+ @handle_errors
45
+ def growth(
46
+ ctx: typer.Context,
47
+ domain: str = typer.Argument(..., help=DOMAIN_HELP),
48
+ fmt: str | None = typer.Option(None, "--format", help=FORMAT_HELP),
49
+ ) -> None:
50
+ """Growth signals for a domain."""
51
+ from discolike_cli.main import get_client
52
+
53
+ emit(get_client(ctx).companies.growth(domain=domain), fmt=fmt)
54
+
55
+
56
+ @app.command()
57
+ @handle_errors
58
+ def redirects(
59
+ ctx: typer.Context,
60
+ domain: str = typer.Argument(..., help=DOMAIN_HELP),
61
+ match: str | None = typer.Option(None, "--match", help=MATCH_HELP),
62
+ fmt: str | None = typer.Option(None, "--format", help=FORMAT_HELP),
63
+ ) -> None:
64
+ """Domain redirects for a company domain."""
65
+ from discolike_cli.main import get_client
66
+
67
+ emit(get_client(ctx).companies.redirects(domain=domain, match=match), fmt=fmt)
68
+
69
+
70
+ @app.command()
71
+ @handle_errors
72
+ def vendors(
73
+ ctx: typer.Context,
74
+ domain: str = typer.Argument(..., help=DOMAIN_HELP),
75
+ match: str | None = typer.Option(None, "--match", help=MATCH_HELP),
76
+ fmt: str | None = typer.Option(None, "--format", help=FORMAT_HELP),
77
+ ) -> None:
78
+ """Vendors associated with a company domain."""
79
+ from discolike_cli.main import get_client
80
+
81
+ emit(get_client(ctx).companies.vendors(domain=domain, match=match), fmt=fmt)
82
+
83
+
84
+ @app.command()
85
+ @handle_errors
86
+ def subsidiaries(
87
+ ctx: typer.Context,
88
+ domain: str = typer.Argument(..., help=DOMAIN_HELP),
89
+ match: str | None = typer.Option(None, "--match", help=MATCH_HELP),
90
+ fmt: str | None = typer.Option(None, "--format", help=FORMAT_HELP),
91
+ ) -> None:
92
+ """Subsidiaries of a company domain."""
93
+ from discolike_cli.main import get_client
94
+
95
+ emit(get_client(ctx).companies.subsidiaries(domain=domain, match=match), fmt=fmt)
96
+
97
+
98
+ @app.command(name="public-links")
99
+ @handle_errors
100
+ def public_links(
101
+ ctx: typer.Context,
102
+ domain: str = typer.Argument(..., help=DOMAIN_HELP),
103
+ source: str = typer.Option(..., "--source", help="Public link source, e.g. crunchbase"),
104
+ fmt: str | None = typer.Option(None, "--format", help=FORMAT_HELP),
105
+ ) -> None:
106
+ """Public profile links for a domain from a given source."""
107
+ from discolike_cli.main import get_client
108
+
109
+ emit(get_client(ctx).companies.public_links(domain=domain, source=source), fmt=fmt)
110
+
111
+
112
+ @handle_errors
113
+ def extract_command(
114
+ ctx: typer.Context,
115
+ url: str = typer.Argument(..., help="Page URL to extract content from"),
116
+ fmt: str | None = typer.Option(None, "--format", help=FORMAT_HELP),
117
+ ) -> None:
118
+ """Extract page content from a URL."""
119
+ from discolike_cli.main import get_client
120
+
121
+ emit(get_client(ctx).companies.extract(url=url), fmt=fmt)