discolike-cli 0.1.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.
- discolike_cli-0.1.0/.gitignore +6 -0
- discolike_cli-0.1.0/PKG-INFO +89 -0
- discolike_cli-0.1.0/README.md +65 -0
- discolike_cli-0.1.0/pyproject.toml +51 -0
- discolike_cli-0.1.0/src/discolike_cli/__init__.py +0 -0
- discolike_cli-0.1.0/src/discolike_cli/_output.py +135 -0
- discolike_cli-0.1.0/src/discolike_cli/account.py +16 -0
- discolike_cli-0.1.0/src/discolike_cli/auth.py +60 -0
- discolike_cli-0.1.0/src/discolike_cli/company.py +87 -0
- discolike_cli-0.1.0/src/discolike_cli/contacts.py +230 -0
- discolike_cli-0.1.0/src/discolike_cli/discogen.py +119 -0
- discolike_cli-0.1.0/src/discolike_cli/discover.py +111 -0
- discolike_cli-0.1.0/src/discolike_cli/enrich.py +91 -0
- discolike_cli-0.1.0/src/discolike_cli/main.py +59 -0
- discolike_cli-0.1.0/src/discolike_cli/match.py +54 -0
- discolike_cli-0.1.0/src/discolike_cli/queries.py +65 -0
- discolike_cli-0.1.0/tests/conftest.py +23 -0
- discolike_cli-0.1.0/tests/test_account.py +29 -0
- discolike_cli-0.1.0/tests/test_auth.py +121 -0
- discolike_cli-0.1.0/tests/test_company.py +117 -0
- discolike_cli-0.1.0/tests/test_contacts.py +286 -0
- discolike_cli-0.1.0/tests/test_discogen.py +203 -0
- discolike_cli-0.1.0/tests/test_discover.py +112 -0
- discolike_cli-0.1.0/tests/test_enrich.py +222 -0
- discolike_cli-0.1.0/tests/test_match.py +178 -0
- discolike_cli-0.1.0/tests/test_output.py +203 -0
- discolike_cli-0.1.0/tests/test_queries_cli.py +102 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: discolike-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official CLI for the DiscoLike API
|
|
5
|
+
Project-URL: Homepage, https://www.discolike.com
|
|
6
|
+
Project-URL: Documentation, https://docs.discolike.com
|
|
7
|
+
Project-URL: Repository, https://github.com/Discolike/discolike-python
|
|
8
|
+
Author-email: DiscoLike <support@discolike.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: business-data,cli,discolike,enrichment
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: discolike>=0.1.0
|
|
21
|
+
Requires-Dist: rich>=13.0
|
|
22
|
+
Requires-Dist: typer>=0.12
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# discolike-cli
|
|
26
|
+
|
|
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.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install discolike-cli
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or run it without installing:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uvx --from discolike-cli discolike --help
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Requires Python 3.10+. Installing this package gives you the `discolike` command.
|
|
42
|
+
|
|
43
|
+
## Authentication
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
discolike auth login
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Prompts for an API key (or pass `--api-key`) and verifies it against your account. Create a key at [app.discolike.com/account/management/keys](https://app.discolike.com/account/management/keys). You can also set `DISCOLIKE_API_KEY` in the environment instead.
|
|
50
|
+
|
|
51
|
+
## Quickstart
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
discolike discover --icp-text "managed IT services for SMBs" --country US --max-records 25
|
|
55
|
+
discolike match "Stripe Inc" --city "San Francisco"
|
|
56
|
+
discolike match --file companies.csv --name-column company_name --wait
|
|
57
|
+
discolike count --phrase-match "book a demo" --country US
|
|
58
|
+
discolike company data stripe.com
|
|
59
|
+
discolike extract https://stripe.com/enterprise
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Top-level commands: `discover`, `count`, `match`, `extract`, `validate-icp`, `append`, `segment` — plus `auth`, `company`, `contacts`, `discogen`, `queries`, and `account` command groups.
|
|
63
|
+
|
|
64
|
+
### Conventions
|
|
65
|
+
|
|
66
|
+
- Results print as JSON to stdout; errors print as JSON (`error`, `message`, `status_code`) to stderr.
|
|
67
|
+
- Pass `--format table` for a human-readable table — used automatically when stdout is a TTY.
|
|
68
|
+
- Async endpoints (`match --file`, `discogen run`, `discogen run-personas`, `segment`, `validate-icp`) take `--wait` to block until the job finishes. Without it, you get a `task_id` back to poll with `discolike discogen status <task_id> --family <family>`. `append` is synchronous — it returns enriched rows directly (or writes CSV bytes to `--output`).
|
|
69
|
+
|
|
70
|
+
### Exit codes
|
|
71
|
+
|
|
72
|
+
| Exit code | Meaning |
|
|
73
|
+
|---|---|
|
|
74
|
+
| 0 | Success |
|
|
75
|
+
| 1 | Server error or unexpected failure |
|
|
76
|
+
| 2 | Validation error |
|
|
77
|
+
| 3 | Authentication or plan-access error |
|
|
78
|
+
| 4 | Rate limited |
|
|
79
|
+
| 5 | Network error |
|
|
80
|
+
| 6 | Not found |
|
|
81
|
+
|
|
82
|
+
## Links
|
|
83
|
+
|
|
84
|
+
- **API documentation**: [docs.discolike.com](https://docs.discolike.com)
|
|
85
|
+
- **Source**: [github.com/Discolike/discolike-python](https://github.com/Discolike/discolike-python)
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
[MIT](https://github.com/Discolike/discolike-python/blob/main/LICENSE)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# discolike-cli
|
|
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.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install discolike-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run it without installing:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uvx --from discolike-cli discolike --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requires Python 3.10+. Installing this package gives you the `discolike` command.
|
|
18
|
+
|
|
19
|
+
## Authentication
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
discolike auth login
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Prompts for an API key (or pass `--api-key`) and verifies it against your account. Create a key at [app.discolike.com/account/management/keys](https://app.discolike.com/account/management/keys). You can also set `DISCOLIKE_API_KEY` in the environment instead.
|
|
26
|
+
|
|
27
|
+
## Quickstart
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
discolike discover --icp-text "managed IT services for SMBs" --country US --max-records 25
|
|
31
|
+
discolike match "Stripe Inc" --city "San Francisco"
|
|
32
|
+
discolike match --file companies.csv --name-column company_name --wait
|
|
33
|
+
discolike count --phrase-match "book a demo" --country US
|
|
34
|
+
discolike company data stripe.com
|
|
35
|
+
discolike extract https://stripe.com/enterprise
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Top-level commands: `discover`, `count`, `match`, `extract`, `validate-icp`, `append`, `segment` — plus `auth`, `company`, `contacts`, `discogen`, `queries`, and `account` command groups.
|
|
39
|
+
|
|
40
|
+
### Conventions
|
|
41
|
+
|
|
42
|
+
- Results print as JSON to stdout; errors print as JSON (`error`, `message`, `status_code`) to stderr.
|
|
43
|
+
- Pass `--format table` for a human-readable table — used automatically when stdout is a TTY.
|
|
44
|
+
- Async endpoints (`match --file`, `discogen run`, `discogen run-personas`, `segment`, `validate-icp`) take `--wait` to block until the job finishes. Without it, you get a `task_id` back to poll with `discolike discogen status <task_id> --family <family>`. `append` is synchronous — it returns enriched rows directly (or writes CSV bytes to `--output`).
|
|
45
|
+
|
|
46
|
+
### Exit codes
|
|
47
|
+
|
|
48
|
+
| Exit code | Meaning |
|
|
49
|
+
|---|---|
|
|
50
|
+
| 0 | Success |
|
|
51
|
+
| 1 | Server error or unexpected failure |
|
|
52
|
+
| 2 | Validation error |
|
|
53
|
+
| 3 | Authentication or plan-access error |
|
|
54
|
+
| 4 | Rate limited |
|
|
55
|
+
| 5 | Network error |
|
|
56
|
+
| 6 | Not found |
|
|
57
|
+
|
|
58
|
+
## Links
|
|
59
|
+
|
|
60
|
+
- **API documentation**: [docs.discolike.com](https://docs.discolike.com)
|
|
61
|
+
- **Source**: [github.com/Discolike/discolike-python](https://github.com/Discolike/discolike-python)
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
[MIT](https://github.com/Discolike/discolike-python/blob/main/LICENSE)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "discolike-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official CLI for the DiscoLike API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "DiscoLike", email = "support@discolike.com" }]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"discolike>=0.1.0",
|
|
15
|
+
"typer>=0.12",
|
|
16
|
+
"rich>=13.0",
|
|
17
|
+
]
|
|
18
|
+
keywords = ["discolike", "cli", "business-data", "enrichment"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 4 - Beta",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
discolike = "discolike_cli.main:app"
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://www.discolike.com"
|
|
35
|
+
Documentation = "https://docs.discolike.com"
|
|
36
|
+
Repository = "https://github.com/Discolike/discolike-python"
|
|
37
|
+
|
|
38
|
+
[tool.uv.sources]
|
|
39
|
+
discolike = { workspace = true }
|
|
40
|
+
|
|
41
|
+
[dependency-groups]
|
|
42
|
+
dev = [
|
|
43
|
+
"pytest>=8",
|
|
44
|
+
"pytest-asyncio>=0.24",
|
|
45
|
+
"ruff>=0.8",
|
|
46
|
+
"ty",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[tool.pytest.ini_options]
|
|
50
|
+
asyncio_mode = "auto"
|
|
51
|
+
testpaths = ["tests"]
|
|
File without changes
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import Any
|
|
8
|
+
from typing import ParamSpec
|
|
9
|
+
from typing import Protocol
|
|
10
|
+
from typing import TypeVar
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
from discolike._exceptions import APIConnectionError
|
|
17
|
+
from discolike._exceptions import AuthenticationError
|
|
18
|
+
from discolike._exceptions import DiscolikeError
|
|
19
|
+
from discolike._exceptions import NotFoundError
|
|
20
|
+
from discolike._exceptions import PlanAccessError
|
|
21
|
+
from discolike._exceptions import RateLimitError
|
|
22
|
+
from discolike._exceptions import ValidationError
|
|
23
|
+
from discolike._models import DiscolikeModel
|
|
24
|
+
|
|
25
|
+
P = ParamSpec("P")
|
|
26
|
+
R = TypeVar("R")
|
|
27
|
+
|
|
28
|
+
EXIT_CODES: dict[type, int] = {
|
|
29
|
+
ValidationError: 2,
|
|
30
|
+
AuthenticationError: 3,
|
|
31
|
+
PlanAccessError: 3,
|
|
32
|
+
RateLimitError: 4,
|
|
33
|
+
APIConnectionError: 5,
|
|
34
|
+
NotFoundError: 6,
|
|
35
|
+
}
|
|
36
|
+
DEFAULT_EXIT_CODE = 1
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class _JobStatusLike(Protocol):
|
|
40
|
+
results: Any
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def progress(self) -> int | None: ...
|
|
44
|
+
|
|
45
|
+
def to_dict(self) -> dict[str, Any]: ...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class SupportsWait(Protocol):
|
|
49
|
+
task_id: str
|
|
50
|
+
task_family: str
|
|
51
|
+
|
|
52
|
+
def wait(self, *, timeout: float, on_poll: Callable[[_JobStatusLike], None] | None = None) -> _JobStatusLike: ...
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _normalize(data: Any) -> Any: # noqa: ANN401 -- accepts arbitrary JSON-serializable CLI output data
|
|
56
|
+
if isinstance(data, DiscolikeModel):
|
|
57
|
+
return data.to_dict()
|
|
58
|
+
if isinstance(data, list):
|
|
59
|
+
return [item.to_dict() if isinstance(item, DiscolikeModel) else item for item in data]
|
|
60
|
+
return data
|
|
61
|
+
|
|
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())
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
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)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _render_table(rows: list[dict[str, Any]]) -> None:
|
|
72
|
+
table = Table()
|
|
73
|
+
columns = list(rows[0].keys())
|
|
74
|
+
for column in columns:
|
|
75
|
+
table.add_column(column)
|
|
76
|
+
for row in rows:
|
|
77
|
+
table.add_row(*(str(row.get(column, "")) for column in columns))
|
|
78
|
+
Console().print(table)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def emit(data: Any, *, fmt: str | None = None) -> None: # noqa: ANN401 -- accepts arbitrary JSON-serializable CLI output data
|
|
82
|
+
normalized = _normalize(data)
|
|
83
|
+
want_table = fmt == "table" or (fmt is None and sys.stdout.isatty())
|
|
84
|
+
if want_table and _qualifies_for_table(normalized):
|
|
85
|
+
_render_table(normalized)
|
|
86
|
+
return
|
|
87
|
+
print(json.dumps(normalized, indent=2, default=str))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def fail(exc: DiscolikeError) -> typer.Exit:
|
|
91
|
+
payload: dict[str, Any] = {
|
|
92
|
+
"error": type(exc).__name__,
|
|
93
|
+
"message": str(exc),
|
|
94
|
+
"status_code": exc.status_code,
|
|
95
|
+
}
|
|
96
|
+
if isinstance(exc, RateLimitError) and exc.retry_after is not None:
|
|
97
|
+
payload["retry_after"] = exc.retry_after
|
|
98
|
+
print(json.dumps(payload), file=sys.stderr)
|
|
99
|
+
return typer.Exit(code=EXIT_CODES.get(type(exc), DEFAULT_EXIT_CODE))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def call_typed(fn: Callable[..., R], **kwargs: Any) -> R: # noqa: ANN401 -- forwarded to a typed SDK method signature
|
|
103
|
+
try:
|
|
104
|
+
return fn(**kwargs)
|
|
105
|
+
except TypeError as exc:
|
|
106
|
+
raise typer.BadParameter(str(exc)) from exc
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def handle_errors(fn: Callable[P, R]) -> Callable[P, R]:
|
|
110
|
+
@functools.wraps(fn)
|
|
111
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
112
|
+
try:
|
|
113
|
+
return fn(*args, **kwargs)
|
|
114
|
+
except DiscolikeError as exc:
|
|
115
|
+
raise fail(exc) from exc
|
|
116
|
+
|
|
117
|
+
return wrapper
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def run_job(job: SupportsWait, *, wait: bool, timeout: float) -> None:
|
|
121
|
+
if not wait:
|
|
122
|
+
emit(
|
|
123
|
+
{
|
|
124
|
+
"task_id": job.task_id,
|
|
125
|
+
"task_family": job.task_family,
|
|
126
|
+
"hint": f"poll with: discolike discogen status {job.task_id} --family {job.task_family}",
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
def _on_poll(status: _JobStatusLike) -> None:
|
|
132
|
+
sys.stderr.write(f"progress: {status.progress}%\n")
|
|
133
|
+
|
|
134
|
+
final = job.wait(timeout=timeout, on_poll=_on_poll)
|
|
135
|
+
emit(final.results if final.results is not None else final.to_dict())
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
app = typer.Typer(help="Account usage and billing")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@app.command("usage")
|
|
12
|
+
@handle_errors
|
|
13
|
+
def usage_command(ctx: typer.Context) -> None:
|
|
14
|
+
from discolike_cli.main import get_client
|
|
15
|
+
|
|
16
|
+
emit(get_client(ctx).account.usage())
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from discolike._config import ENV_API_KEY
|
|
10
|
+
from discolike._config import KEYS_URL
|
|
11
|
+
from discolike._config import delete_config
|
|
12
|
+
from discolike._config import load_config
|
|
13
|
+
from discolike._config import resolve_api_key
|
|
14
|
+
from discolike._config import save_config
|
|
15
|
+
from discolike_cli._output import emit
|
|
16
|
+
from discolike_cli._output import handle_errors
|
|
17
|
+
|
|
18
|
+
app = typer.Typer(help="Manage API credentials")
|
|
19
|
+
|
|
20
|
+
MASKED_VISIBLE_CHARS = 4
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _mask(key: str) -> str:
|
|
24
|
+
return "…" + key[-MASKED_VISIBLE_CHARS:]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.command()
|
|
28
|
+
@handle_errors
|
|
29
|
+
def login(
|
|
30
|
+
api_key: str | None = typer.Option(None, help=f"API key. Create one at {KEYS_URL}"),
|
|
31
|
+
) -> None:
|
|
32
|
+
from discolike_cli.main import build_client
|
|
33
|
+
|
|
34
|
+
key = api_key or typer.prompt("API key", hide_input=True)
|
|
35
|
+
build_client(api_key=key).account.usage()
|
|
36
|
+
save_config({"auth_method": "api_key", "api_key": key})
|
|
37
|
+
print(json.dumps({"logged_in": True, "source": "api_key"}), file=sys.stderr)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@app.command()
|
|
41
|
+
@handle_errors
|
|
42
|
+
def status() -> None:
|
|
43
|
+
from discolike_cli.main import build_client
|
|
44
|
+
|
|
45
|
+
key = os.environ.get(ENV_API_KEY)
|
|
46
|
+
source = "env"
|
|
47
|
+
if not key:
|
|
48
|
+
key = load_config().get("api_key")
|
|
49
|
+
source = "config"
|
|
50
|
+
if not key:
|
|
51
|
+
resolve_api_key(None)
|
|
52
|
+
build_client(api_key=key).account.usage()
|
|
53
|
+
emit({"source": source, "api_key": _mask(str(key)), "valid": True})
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@app.command()
|
|
57
|
+
@handle_errors
|
|
58
|
+
def logout() -> None:
|
|
59
|
+
delete_config()
|
|
60
|
+
emit({"logged_out": True})
|
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
app = typer.Typer(help="Look up company data by domain")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@app.command()
|
|
12
|
+
@handle_errors
|
|
13
|
+
def data(ctx: typer.Context, domain: str) -> None:
|
|
14
|
+
from discolike_cli.main import get_client
|
|
15
|
+
|
|
16
|
+
emit(get_client(ctx).companies.data(domain=domain))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@app.command()
|
|
20
|
+
@handle_errors
|
|
21
|
+
def score(ctx: typer.Context, domain: str) -> None:
|
|
22
|
+
from discolike_cli.main import get_client
|
|
23
|
+
|
|
24
|
+
emit(get_client(ctx).companies.score(domain=domain))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.command()
|
|
28
|
+
@handle_errors
|
|
29
|
+
def growth(ctx: typer.Context, domain: str) -> None:
|
|
30
|
+
from discolike_cli.main import get_client
|
|
31
|
+
|
|
32
|
+
emit(get_client(ctx).companies.growth(domain=domain))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.command()
|
|
36
|
+
@handle_errors
|
|
37
|
+
def metrics(ctx: typer.Context, domain: str) -> None:
|
|
38
|
+
from discolike_cli.main import get_client
|
|
39
|
+
|
|
40
|
+
emit(get_client(ctx).companies.metrics(domain=domain))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.command()
|
|
44
|
+
@handle_errors
|
|
45
|
+
def history(ctx: typer.Context, domain: str, max_records: int | None = typer.Option(None, "--max-records")) -> None:
|
|
46
|
+
from discolike_cli.main import get_client
|
|
47
|
+
|
|
48
|
+
emit(get_client(ctx).companies.history(domain=domain, max_records=max_records))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.command()
|
|
52
|
+
@handle_errors
|
|
53
|
+
def redirects(ctx: typer.Context, domain: str, match: str | None = typer.Option(None, "--match")) -> None:
|
|
54
|
+
from discolike_cli.main import get_client
|
|
55
|
+
|
|
56
|
+
emit(get_client(ctx).companies.redirects(domain=domain, match=match))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@app.command()
|
|
60
|
+
@handle_errors
|
|
61
|
+
def vendors(ctx: typer.Context, domain: str, match: str | None = typer.Option(None, "--match")) -> None:
|
|
62
|
+
from discolike_cli.main import get_client
|
|
63
|
+
|
|
64
|
+
emit(get_client(ctx).companies.vendors(domain=domain, match=match))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@app.command()
|
|
68
|
+
@handle_errors
|
|
69
|
+
def subsidiaries(ctx: typer.Context, domain: str, match: str | None = typer.Option(None, "--match")) -> None:
|
|
70
|
+
from discolike_cli.main import get_client
|
|
71
|
+
|
|
72
|
+
emit(get_client(ctx).companies.subsidiaries(domain=domain, match=match))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@app.command(name="public-links")
|
|
76
|
+
@handle_errors
|
|
77
|
+
def public_links(ctx: typer.Context, domain: str, source: str = typer.Option(..., "--source")) -> None:
|
|
78
|
+
from discolike_cli.main import get_client
|
|
79
|
+
|
|
80
|
+
emit(get_client(ctx).companies.public_links(domain=domain, source=source))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@handle_errors
|
|
84
|
+
def extract_command(ctx: typer.Context, url: str) -> None:
|
|
85
|
+
from discolike_cli.main import get_client
|
|
86
|
+
|
|
87
|
+
emit(get_client(ctx).companies.extract(url=url))
|