scrapebadger-cli 0.2.1__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 (28) hide show
  1. scrapebadger_cli-0.2.1/.gitignore +188 -0
  2. scrapebadger_cli-0.2.1/PKG-INFO +41 -0
  3. scrapebadger_cli-0.2.1/README.md +19 -0
  4. scrapebadger_cli-0.2.1/pyproject.toml +38 -0
  5. scrapebadger_cli-0.2.1/src/scrapebadger_cli/__init__.py +1 -0
  6. scrapebadger_cli-0.2.1/src/scrapebadger_cli/client.py +57 -0
  7. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/__init__.py +1 -0
  8. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/amazon.py +227 -0
  9. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/auth.py +52 -0
  10. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/ebay.py +217 -0
  11. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/google.py +1117 -0
  12. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/idealista.py +220 -0
  13. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/immobiliare.py +151 -0
  14. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/leboncoin.py +154 -0
  15. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/loopnet.py +89 -0
  16. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/realtor.py +91 -0
  17. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/reddit.py +173 -0
  18. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/stream.py +270 -0
  19. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/tiktok.py +283 -0
  20. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/twitter.py +235 -0
  21. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/vinted.py +105 -0
  22. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/web.py +117 -0
  23. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/youtube.py +625 -0
  24. scrapebadger_cli-0.2.1/src/scrapebadger_cli/commands/zillow.py +105 -0
  25. scrapebadger_cli-0.2.1/src/scrapebadger_cli/config.py +69 -0
  26. scrapebadger_cli-0.2.1/src/scrapebadger_cli/main.py +52 -0
  27. scrapebadger_cli-0.2.1/src/scrapebadger_cli/output.py +116 -0
  28. scrapebadger_cli-0.2.1/uv.lock +410 -0
@@ -0,0 +1,188 @@
1
+ # =============================================================================
2
+ # ScrapeBadger - Git Ignore
3
+ # =============================================================================
4
+
5
+ # -----------------------------------------------------------------------------
6
+ # Python
7
+ # -----------------------------------------------------------------------------
8
+ __pycache__/
9
+ *.py[cod]
10
+ *$py.class
11
+ *.so
12
+ .Python
13
+ build/
14
+ develop-eggs/
15
+ dist/
16
+ downloads/
17
+ eggs/
18
+ .eggs/
19
+ # Python build artifacts (root level only, not src/lib/)
20
+ /lib/
21
+ /lib64/
22
+ parts/
23
+ sdist/
24
+ var/
25
+ wheels/
26
+ *.egg-info/
27
+ .installed.cfg
28
+ *.egg
29
+ MANIFEST
30
+
31
+ # Virtual environments
32
+ .venv/
33
+ venv/
34
+ ENV/
35
+ env/
36
+
37
+ # UV
38
+ .uv/
39
+ # Note: uv.lock is committed for reproducible builds
40
+
41
+ # Pytest
42
+ .pytest_cache/
43
+ htmlcov/
44
+ .coverage
45
+ .coverage.*
46
+ coverage.xml
47
+ *.cover
48
+ .hypothesis/
49
+
50
+ # mypy
51
+ .mypy_cache/
52
+ .dmypy.json
53
+ dmypy.json
54
+
55
+ # Ruff
56
+ .ruff_cache/
57
+
58
+ # -----------------------------------------------------------------------------
59
+ # Node.js / Next.js
60
+ # -----------------------------------------------------------------------------
61
+ node_modules/
62
+ .next/
63
+ out/
64
+ .nuxt/
65
+ dist/
66
+ npm-debug.log*
67
+ yarn-debug.log*
68
+ yarn-error.log*
69
+ .pnpm-debug.log*
70
+
71
+ # TypeScript
72
+ *.tsbuildinfo
73
+
74
+ # -----------------------------------------------------------------------------
75
+ # Environment & Secrets
76
+ # -----------------------------------------------------------------------------
77
+ .env
78
+ .env.local
79
+ .env.*.local
80
+ .env.development
81
+ .env.test
82
+ .env.production
83
+ *.pem
84
+ *.key
85
+ secrets/
86
+ .secrets/
87
+
88
+ # -----------------------------------------------------------------------------
89
+ # IDE & Editors
90
+ # -----------------------------------------------------------------------------
91
+ .idea/
92
+ .vscode/
93
+ *.swp
94
+ *.swo
95
+ *~
96
+ .project
97
+ .classpath
98
+ .settings/
99
+ *.sublime-workspace
100
+ *.sublime-project
101
+
102
+ # -----------------------------------------------------------------------------
103
+ # OS
104
+ # -----------------------------------------------------------------------------
105
+ .DS_Store
106
+ .DS_Store?
107
+ ._*
108
+ .Spotlight-V100
109
+ .Trashes
110
+ ehthumbs.db
111
+ Thumbs.db
112
+
113
+ # -----------------------------------------------------------------------------
114
+ # Docker
115
+ # -----------------------------------------------------------------------------
116
+ docker-compose.override.yml
117
+ .docker/
118
+
119
+ # -----------------------------------------------------------------------------
120
+ # Terraform
121
+ # -----------------------------------------------------------------------------
122
+ *.tfstate
123
+ *.tfstate.*
124
+ *.tfstate.backup
125
+ .terraform/
126
+ .terraform.lock.hcl
127
+ terraform.tfvars
128
+ *.auto.tfvars
129
+ crash.log
130
+ crash.*.log
131
+ override.tf
132
+ override.tf.json
133
+ *_override.tf
134
+ *_override.tf.json
135
+ github-secrets.env
136
+
137
+ # -----------------------------------------------------------------------------
138
+ # Logs & Temp Files
139
+ # -----------------------------------------------------------------------------
140
+ *.log
141
+ logs/
142
+ !apps/portal/src/**/logs/
143
+ tmp/
144
+ temp/
145
+ *.tmp
146
+ *.bak
147
+
148
+ # -----------------------------------------------------------------------------
149
+ # Build Artifacts
150
+ # -----------------------------------------------------------------------------
151
+ *.pyc
152
+ *.pyo
153
+ *.exe
154
+ *.dll
155
+ *.dylib
156
+
157
+ # -----------------------------------------------------------------------------
158
+ # Project Specific
159
+ # -----------------------------------------------------------------------------
160
+
161
+ # Database backups
162
+ backups/
163
+
164
+ # Local data (root-only; frontend data/ dirs like apps/portal/src/data
165
+ # are first-class source)
166
+ /data/
167
+ *.db
168
+ *.sqlite
169
+ *.sqlite3
170
+
171
+ # Jupyter
172
+ .ipynb_checkpoints/
173
+ *.ipynb
174
+
175
+ # Test outputs
176
+ test-results/
177
+ playwright-report/
178
+
179
+ # Playwright MCP screenshots
180
+ .playwright-mcp/
181
+
182
+ # Claude Code worktrees (per-task scratch checkouts)
183
+ .claude/worktrees/
184
+
185
+ # TikTok reverse-engineering research scratch (large: full APK, decompiled
186
+ # proprietary binaries, native .so libs, frida-server, handoff tarballs).
187
+ # Local-only — not part of the deployable codebase.
188
+ research/
@@ -0,0 +1,41 @@
1
+ Metadata-Version: 2.4
2
+ Name: scrapebadger-cli
3
+ Version: 0.2.1
4
+ Summary: ScrapeBadger CLI — Twitter, Vinted, Google, and Web Scraping from the command line
5
+ Author: ScrapeBadger Team
6
+ License: MIT
7
+ Keywords: ai-tools,cli,mcp,scrapebadger,twitter,vinted,web-scraping
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Topic :: Internet :: WWW/HTTP
12
+ Classifier: Topic :: Software Development :: Libraries
13
+ Requires-Python: >=3.10
14
+ Requires-Dist: httpx>=0.26.0
15
+ Requires-Dist: rich>=13.0.0
16
+ Requires-Dist: typer>=0.12.0
17
+ Requires-Dist: websockets>=12.0
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
20
+ Requires-Dist: ruff>=0.2.0; extra == 'dev'
21
+ Description-Content-Type: text/markdown
22
+
23
+ # ScrapeBadger CLI
24
+
25
+ Command-line interface for ScrapeBadger — Twitter, Vinted, and Web Scraping.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install scrapebadger-cli
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ```bash
36
+ scrapebadger auth --api-key sb_live_...
37
+ scrapebadger twitter user elonmusk
38
+ scrapebadger twitter search "AI agents" --output table
39
+ scrapebadger vinted search "nike shoes" --market de
40
+ scrapebadger web scrape https://example.com --js
41
+ ```
@@ -0,0 +1,19 @@
1
+ # ScrapeBadger CLI
2
+
3
+ Command-line interface for ScrapeBadger — Twitter, Vinted, and Web Scraping.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install scrapebadger-cli
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ scrapebadger auth --api-key sb_live_...
15
+ scrapebadger twitter user elonmusk
16
+ scrapebadger twitter search "AI agents" --output table
17
+ scrapebadger vinted search "nike shoes" --market de
18
+ scrapebadger web scrape https://example.com --js
19
+ ```
@@ -0,0 +1,38 @@
1
+ [project]
2
+ name = "scrapebadger-cli"
3
+ version = "0.2.1"
4
+ description = "ScrapeBadger CLI — Twitter, Vinted, Google, and Web Scraping from the command line"
5
+ requires-python = ">=3.10"
6
+ license = {text = "MIT"}
7
+ authors = [{name = "ScrapeBadger Team"}]
8
+ readme = "README.md"
9
+ keywords = ["scrapebadger", "cli", "web-scraping", "twitter", "vinted", "mcp", "ai-tools"]
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "Environment :: Console",
13
+ "Intended Audience :: Developers",
14
+ "Topic :: Internet :: WWW/HTTP",
15
+ "Topic :: Software Development :: Libraries",
16
+ ]
17
+ dependencies = [
18
+ "typer>=0.12.0",
19
+ "httpx>=0.26.0",
20
+ "rich>=13.0.0",
21
+ "websockets>=12.0",
22
+ ]
23
+
24
+ [project.scripts]
25
+ scrapebadger = "scrapebadger_cli.main:app"
26
+
27
+ [project.optional-dependencies]
28
+ dev = [
29
+ "pytest>=8.0.0",
30
+ "ruff>=0.2.0",
31
+ ]
32
+
33
+ [build-system]
34
+ requires = ["hatchling"]
35
+ build-backend = "hatchling.build"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/scrapebadger_cli"]
@@ -0,0 +1 @@
1
+ """ScrapeBadger CLI — Twitter, Vinted, and Web Scraping from the command line."""
@@ -0,0 +1,57 @@
1
+ """HTTP client for ScrapeBadger API."""
2
+
3
+ import os
4
+ from typing import Any
5
+
6
+ import httpx
7
+ import typer
8
+
9
+ from scrapebadger_cli.config import get_api_key
10
+
11
+ BASE_URL = os.environ.get("SCRAPEBADGER_API_URL", "https://scrapebadger.com")
12
+ _client: httpx.Client | None = None
13
+
14
+
15
+ def _get_client() -> httpx.Client:
16
+ global _client
17
+ if _client is None:
18
+ api_key = get_api_key()
19
+ if not api_key:
20
+ typer.echo(
21
+ "No API key found. Set SCRAPEBADGER_API_KEY or run: scrapebadger auth",
22
+ err=True,
23
+ )
24
+ raise typer.Exit(1)
25
+ _client = httpx.Client(
26
+ base_url=BASE_URL,
27
+ headers={"X-API-Key": api_key},
28
+ timeout=300.0,
29
+ )
30
+ return _client
31
+
32
+
33
+ def api_get(path: str, params: dict[str, Any] | None = None) -> dict | list:
34
+ """Make a GET request to the ScrapeBadger API."""
35
+ clean = {k: v for k, v in (params or {}).items() if v is not None}
36
+ resp = _get_client().get(path, params=clean or None)
37
+ return _handle_response(resp)
38
+
39
+
40
+ def api_post(path: str, json_body: dict[str, Any] | None = None) -> dict | list:
41
+ """Make a POST request to the ScrapeBadger API."""
42
+ resp = _get_client().post(path, json=json_body)
43
+ return _handle_response(resp)
44
+
45
+
46
+ def _handle_response(resp: httpx.Response) -> dict | list:
47
+ """Handle API response, exit on errors."""
48
+ if resp.status_code >= 400:
49
+ try:
50
+ detail = resp.json().get("detail", resp.text)
51
+ except Exception:
52
+ detail = resp.text
53
+ typer.echo(f"Error ({resp.status_code}): {detail}", err=True)
54
+ raise typer.Exit(1)
55
+ if resp.status_code == 204:
56
+ return {"status": "deleted"}
57
+ return resp.json()
@@ -0,0 +1 @@
1
+ """CLI command modules."""
@@ -0,0 +1,227 @@
1
+ """Amazon marketplace commands."""
2
+
3
+ import typer
4
+
5
+ from scrapebadger_cli.client import api_get
6
+ from scrapebadger_cli.output import render
7
+
8
+ app = typer.Typer(no_args_is_help=True)
9
+
10
+ FMT = typer.Option("json", "--output", "-o", help="Output format: json, csv, table, markdown")
11
+ FIELDS = typer.Option(None, "--fields", "-f", help="Comma-separated fields to include")
12
+ DOMAIN = typer.Option("com", "--domain", "-d", help="Amazon marketplace TLD (com, co.uk, de, etc.)")
13
+
14
+
15
+ @app.command()
16
+ def search(
17
+ query: str = typer.Argument(..., help="Search text"),
18
+ domain: str = DOMAIN,
19
+ page: int = typer.Option(1, "--page", "-p"),
20
+ sort_by: str | None = typer.Option(None, "--sort-by", help="e.g. price_low_to_high"),
21
+ category: str | None = typer.Option(None, "--category", help="Department/search alias"),
22
+ min_price: float | None = typer.Option(None, "--min-price"),
23
+ max_price: float | None = typer.Option(None, "--max-price"),
24
+ zip_code: str | None = typer.Option(None, "--zip", help="Delivery ZIP/postal code"),
25
+ fmt: str = FMT,
26
+ fields: str | None = FIELDS,
27
+ ) -> None:
28
+ """Search the Amazon catalog for products."""
29
+ data = api_get(
30
+ "/v1/amazon/search",
31
+ {
32
+ "query": query,
33
+ "domain": domain,
34
+ "page": page,
35
+ "sort_by": sort_by,
36
+ "category": category,
37
+ "min_price": min_price,
38
+ "max_price": max_price,
39
+ "zip": zip_code,
40
+ },
41
+ )
42
+ render(data, fmt, fields)
43
+
44
+
45
+ @app.command()
46
+ def autocomplete(
47
+ query: str = typer.Argument(..., help="Partial search text"),
48
+ domain: str = DOMAIN,
49
+ fmt: str = FMT,
50
+ fields: str | None = FIELDS,
51
+ ) -> None:
52
+ """Get Amazon keyword autocomplete suggestions."""
53
+ data = api_get("/v1/amazon/autocomplete", {"query": query, "domain": domain})
54
+ render(data, fmt, fields)
55
+
56
+
57
+ @app.command()
58
+ def product(
59
+ asin: str = typer.Argument(..., help="Amazon ASIN"),
60
+ domain: str = DOMAIN,
61
+ zip_code: str | None = typer.Option(None, "--zip", help="Delivery ZIP/postal code"),
62
+ language: str | None = typer.Option(None, "--language", help="e.g. en_US"),
63
+ fmt: str = FMT,
64
+ fields: str | None = FIELDS,
65
+ ) -> None:
66
+ """Get a full product detail page (PDP) by ASIN."""
67
+ data = api_get(
68
+ f"/v1/amazon/products/{asin}",
69
+ {"domain": domain, "zip": zip_code, "language": language},
70
+ )
71
+ render(data, fmt, fields)
72
+
73
+
74
+ @app.command()
75
+ def offers(
76
+ asin: str = typer.Argument(..., help="Amazon ASIN"),
77
+ domain: str = DOMAIN,
78
+ zip_code: str | None = typer.Option(None, "--zip", help="Delivery ZIP/postal code"),
79
+ fmt: str = FMT,
80
+ fields: str | None = FIELDS,
81
+ ) -> None:
82
+ """Get all seller offers (buybox + competing) for a product."""
83
+ data = api_get(f"/v1/amazon/products/{asin}/offers", {"domain": domain, "zip": zip_code})
84
+ render(data, fmt, fields)
85
+
86
+
87
+ @app.command()
88
+ def reviews(
89
+ asin: str = typer.Argument(..., help="Amazon ASIN"),
90
+ domain: str = DOMAIN,
91
+ page: int = typer.Option(1, "--page", "-p"),
92
+ sort_by: str | None = typer.Option(None, "--sort-by", help="helpful or recent"),
93
+ star: str | None = typer.Option(None, "--star", help="e.g. five_star, critical"),
94
+ verified_only: bool = typer.Option(False, "--verified-only"),
95
+ media_only: bool = typer.Option(False, "--media-only"),
96
+ fmt: str = FMT,
97
+ fields: str | None = FIELDS,
98
+ ) -> None:
99
+ """Get customer reviews for a product."""
100
+ data = api_get(
101
+ f"/v1/amazon/products/{asin}/reviews",
102
+ {
103
+ "domain": domain,
104
+ "page": page,
105
+ "sort_by": sort_by,
106
+ "star": star,
107
+ "verified_only": verified_only,
108
+ "media_only": media_only,
109
+ },
110
+ )
111
+ render(data, fmt, fields)
112
+
113
+
114
+ @app.command()
115
+ def bestsellers(
116
+ domain: str = DOMAIN,
117
+ category: str | None = typer.Option(None, "--category", help="Browse node ID"),
118
+ page: int = typer.Option(1, "--page", "-p"),
119
+ fmt: str = FMT,
120
+ fields: str | None = FIELDS,
121
+ ) -> None:
122
+ """Get an Amazon Best Sellers list."""
123
+ data = api_get("/v1/amazon/bestsellers", {"domain": domain, "category": category, "page": page})
124
+ render(data, fmt, fields)
125
+
126
+
127
+ @app.command(name="new-releases")
128
+ def new_releases(
129
+ domain: str = DOMAIN,
130
+ category: str | None = typer.Option(None, "--category", help="Browse node ID"),
131
+ page: int = typer.Option(1, "--page", "-p"),
132
+ fmt: str = FMT,
133
+ fields: str | None = FIELDS,
134
+ ) -> None:
135
+ """Get an Amazon New Releases list."""
136
+ data = api_get(
137
+ "/v1/amazon/new-releases", {"domain": domain, "category": category, "page": page}
138
+ )
139
+ render(data, fmt, fields)
140
+
141
+
142
+ @app.command()
143
+ def deals(
144
+ domain: str = DOMAIN,
145
+ category: str | None = typer.Option(None, "--category", help="Department/category"),
146
+ page: int = typer.Option(1, "--page", "-p"),
147
+ fmt: str = FMT,
148
+ fields: str | None = FIELDS,
149
+ ) -> None:
150
+ """Get current Amazon deals (Today's Deals / Lightning Deals)."""
151
+ data = api_get("/v1/amazon/deals", {"domain": domain, "category": category, "page": page})
152
+ render(data, fmt, fields)
153
+
154
+
155
+ @app.command()
156
+ def category(
157
+ node: str = typer.Argument(..., help="Amazon browse node ID"),
158
+ domain: str = DOMAIN,
159
+ page: int = typer.Option(1, "--page", "-p"),
160
+ sort_by: str | None = typer.Option(None, "--sort-by"),
161
+ fmt: str = FMT,
162
+ fields: str | None = FIELDS,
163
+ ) -> None:
164
+ """Browse an Amazon category/department by browse-node ID."""
165
+ data = api_get(
166
+ "/v1/amazon/category",
167
+ {"node": node, "domain": domain, "page": page, "sort_by": sort_by},
168
+ )
169
+ render(data, fmt, fields)
170
+
171
+
172
+ @app.command()
173
+ def seller(
174
+ seller_id: str = typer.Argument(..., help="Amazon seller ID"),
175
+ domain: str = DOMAIN,
176
+ fmt: str = FMT,
177
+ fields: str | None = FIELDS,
178
+ ) -> None:
179
+ """Get an Amazon seller profile (ratings + feedback summary)."""
180
+ data = api_get(f"/v1/amazon/sellers/{seller_id}", {"domain": domain})
181
+ render(data, fmt, fields)
182
+
183
+
184
+ @app.command(name="seller-products")
185
+ def seller_products(
186
+ seller_id: str = typer.Argument(..., help="Amazon seller ID"),
187
+ domain: str = DOMAIN,
188
+ page: int = typer.Option(1, "--page", "-p"),
189
+ fmt: str = FMT,
190
+ fields: str | None = FIELDS,
191
+ ) -> None:
192
+ """Get the storefront product listings of a seller."""
193
+ data = api_get(f"/v1/amazon/sellers/{seller_id}/products", {"domain": domain, "page": page})
194
+ render(data, fmt, fields)
195
+
196
+
197
+ @app.command(name="seller-feedback")
198
+ def seller_feedback(
199
+ seller_id: str = typer.Argument(..., help="Amazon seller ID"),
200
+ domain: str = DOMAIN,
201
+ page: int = typer.Option(1, "--page", "-p"),
202
+ fmt: str = FMT,
203
+ fields: str | None = FIELDS,
204
+ ) -> None:
205
+ """Get buyer feedback entries for a seller."""
206
+ data = api_get(f"/v1/amazon/sellers/{seller_id}/feedback", {"domain": domain, "page": page})
207
+ render(data, fmt, fields)
208
+
209
+
210
+ @app.command()
211
+ def markets(
212
+ fmt: str = FMT,
213
+ fields: str | None = FIELDS,
214
+ ) -> None:
215
+ """List all supported Amazon marketplaces."""
216
+ data = api_get("/v1/amazon/markets")
217
+ render(data, fmt, fields)
218
+
219
+
220
+ @app.command()
221
+ def categories(
222
+ fmt: str = FMT,
223
+ fields: str | None = FIELDS,
224
+ ) -> None:
225
+ """List reference Amazon departments / category aliases."""
226
+ data = api_get("/v1/amazon/categories")
227
+ render(data, fmt, fields)
@@ -0,0 +1,52 @@
1
+ """Auth and account commands."""
2
+
3
+ import typer
4
+
5
+ from scrapebadger_cli.client import api_get
6
+ from scrapebadger_cli.config import clear_api_key, get_api_key, save_api_key
7
+ from scrapebadger_cli.output import console
8
+
9
+
10
+ def auth(
11
+ api_key: str = typer.Option("", "--api-key", "-k", help="API key (or enter interactively)"),
12
+ show: bool = typer.Option(False, "--show", help="Show current saved API key"),
13
+ ) -> None:
14
+ """Set up or view your ScrapeBadger API key."""
15
+ if show:
16
+ key = get_api_key()
17
+ if key:
18
+ console.print(f"[green]Current API key:[/green] {key[:12]}...{key[-4:]}")
19
+ else:
20
+ console.print("[yellow]No API key configured.[/yellow]")
21
+ return
22
+
23
+ if not api_key:
24
+ api_key = typer.prompt("Enter your ScrapeBadger API key")
25
+
26
+ api_key = api_key.strip()
27
+ if not api_key:
28
+ console.print("[red]API key cannot be empty.[/red]")
29
+ raise typer.Exit(1)
30
+
31
+ save_api_key(api_key)
32
+ console.print(f"[green]API key saved.[/green] ({api_key[:12]}...{api_key[-4:]})")
33
+
34
+
35
+ def credits() -> None:
36
+ """Check your credit balance."""
37
+ data = api_get("/v1/account/me")
38
+ if isinstance(data, dict):
39
+ balance = data.get("credits_balance", "unknown")
40
+ tier = data.get("tier", "unknown")
41
+ rate_limit = data.get("rate_limit_per_minute", "unknown")
42
+ console.print(f"[green]Credits balance:[/green] {balance}")
43
+ console.print(f"[dim]Tier:[/dim] {tier}")
44
+ console.print(f"[dim]Rate limit:[/dim] {rate_limit}/min")
45
+ else:
46
+ console.print(data)
47
+
48
+
49
+ def logout() -> None:
50
+ """Remove saved API key."""
51
+ clear_api_key()
52
+ console.print("[green]API key removed.[/green]")