scrapebadger-cli 0.25.0__tar.gz → 0.26.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 (68) hide show
  1. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/PKG-INFO +1 -1
  2. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/pyproject.toml +1 -1
  3. scrapebadger_cli-0.26.0/src/scrapebadger_cli/commands/copilot.py +87 -0
  4. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/main.py +2 -0
  5. scrapebadger_cli-0.26.0/tests/test_copilot_commands.py +42 -0
  6. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/uv.lock +1 -1
  7. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/.gitignore +0 -0
  8. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/README.md +0 -0
  9. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/__init__.py +0 -0
  10. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/client.py +0 -0
  11. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/__init__.py +0 -0
  12. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/agent.py +0 -0
  13. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/airbnb.py +0 -0
  14. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/aliexpress.py +0 -0
  15. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/amazon.py +0 -0
  16. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/apartments.py +0 -0
  17. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/app_store.py +0 -0
  18. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/asos.py +0 -0
  19. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/auth.py +0 -0
  20. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/baidu.py +0 -0
  21. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/bestbuy.py +0 -0
  22. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/bing.py +0 -0
  23. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/booking.py +0 -0
  24. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/chatgpt.py +0 -0
  25. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/depop.py +0 -0
  26. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/duckduckgo.py +0 -0
  27. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/ebay.py +0 -0
  28. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/etsy.py +0 -0
  29. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/facebook.py +0 -0
  30. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/gemini.py +0 -0
  31. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/google.py +0 -0
  32. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/google_play.py +0 -0
  33. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/gumtree.py +0 -0
  34. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/idealista.py +0 -0
  35. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/immobiliare.py +0 -0
  36. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/init.py +0 -0
  37. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/instagram.py +0 -0
  38. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/leboncoin.py +0 -0
  39. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/linkedin.py +0 -0
  40. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/loopnet.py +0 -0
  41. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/naver.py +0 -0
  42. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/onboarding.py +0 -0
  43. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/perplexity.py +0 -0
  44. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/realtor.py +0 -0
  45. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/reddit.py +0 -0
  46. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/redfin.py +0 -0
  47. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/stream.py +0 -0
  48. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/target.py +0 -0
  49. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/tiktok.py +0 -0
  50. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/twitter.py +0 -0
  51. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/vinted.py +0 -0
  52. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/wallapop.py +0 -0
  53. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/walmart.py +0 -0
  54. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/web.py +0 -0
  55. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/yahoo.py +0 -0
  56. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/yandex.py +0 -0
  57. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/youtube.py +0 -0
  58. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/commands/zillow.py +0 -0
  59. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/config.py +0 -0
  60. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/output.py +0 -0
  61. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/results.py +0 -0
  62. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/src/scrapebadger_cli/views.py +0 -0
  63. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/tests/test_agent_features.py +0 -0
  64. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/tests/test_google_light_search.py +0 -0
  65. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/tests/test_naver.py +0 -0
  66. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/tests/test_perplexity_strict_country.py +0 -0
  67. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/tests/test_tiktok_pagination.py +0 -0
  68. {scrapebadger_cli-0.25.0 → scrapebadger_cli-0.26.0}/tests/test_vinted_image_search.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: scrapebadger-cli
3
- Version: 0.25.0
3
+ Version: 0.26.0
4
4
  Summary: ScrapeBadger CLI — Twitter, Vinted, Google, and Web Scraping from the command line
5
5
  Author: ScrapeBadger Team
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "scrapebadger-cli"
3
- version = "0.25.0"
3
+ version = "0.26.0"
4
4
  description = "ScrapeBadger CLI — Twitter, Vinted, Google, and Web Scraping from the command line"
5
5
  requires-python = ">=3.10"
6
6
  license = {text = "MIT"}
@@ -0,0 +1,87 @@
1
+ """Microsoft Copilot answer-engine 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
+ COUNTRY = typer.Option(
13
+ None,
14
+ "--country",
15
+ "-c",
16
+ help="ISO-3166 alpha-2 country to ask from; also sets the market unless --market. "
17
+ "Best-effort: another country may answer (the response `country` says which) unless "
18
+ "--strict-country is set",
19
+ )
20
+ MARKET = typer.Option(
21
+ None, "--market", "-m", help="Bing market, e.g. ja-JP or en-GB (default: the country's)"
22
+ )
23
+ STRICT = typer.Option(
24
+ False,
25
+ "--strict-country",
26
+ help="Answer only from an exit in --country; fails with 503 country_unavailable "
27
+ "(not billed) when none answers in time",
28
+ )
29
+
30
+
31
+ def _locale(country: str | None, market: str | None, strict_country: bool) -> dict:
32
+ params: dict = {"country": country, "market": market}
33
+ return {**params, "strict_country": True} if strict_country else params
34
+
35
+
36
+ @app.command()
37
+ def ask(
38
+ prompt: str = typer.Argument(..., help="Prompt to send to Copilot (max 2000 chars)"),
39
+ country: str | None = COUNTRY,
40
+ market: str | None = MARKET,
41
+ strict_country: bool = STRICT,
42
+ fmt: str = FMT,
43
+ fields: str | None = FIELDS,
44
+ ) -> None:
45
+ """Ask Microsoft Copilot (Copilot Search in Bing) and get the answer with its sources."""
46
+ data = api_get(
47
+ "/v1/copilot/ask", {"prompt": prompt, **_locale(country, market, strict_country)}
48
+ )
49
+ render(data, fmt, fields)
50
+
51
+
52
+ @app.command(name="brand")
53
+ def brand_visibility(
54
+ prompt: str = typer.Argument(..., help="Buyer-intent prompt, e.g. 'best CRM for startups'"),
55
+ brand: str = typer.Option(..., "--brand", "-b", help="Brand name to look for"),
56
+ domain: str | None = typer.Option(None, "--domain", "-d", help="Brand domain, for citations"),
57
+ aliases: str | None = typer.Option(
58
+ None, "--aliases", help="Comma-separated alternative brand spellings"
59
+ ),
60
+ competitors: str | None = typer.Option(
61
+ None, "--competitors", help="Comma-separated competitor names"
62
+ ),
63
+ country: str | None = COUNTRY,
64
+ market: str | None = MARKET,
65
+ strict_country: bool = STRICT,
66
+ fmt: str = FMT,
67
+ fields: str | None = FIELDS,
68
+ ) -> None:
69
+ """Score how a brand appears in a real Copilot answer (AEO/GEO)."""
70
+ data = api_get(
71
+ "/v1/copilot/brand-visibility",
72
+ {
73
+ "prompt": prompt,
74
+ "brand": brand,
75
+ "domain": domain,
76
+ "aliases": aliases,
77
+ "competitors": competitors,
78
+ **_locale(country, market, strict_country),
79
+ },
80
+ )
81
+ render(data, fmt, fields)
82
+
83
+
84
+ @app.command()
85
+ def markets(fmt: str = FMT, fields: str | None = FIELDS) -> None:
86
+ """List the Bing markets Copilot can be asked in."""
87
+ render(api_get("/v1/copilot/markets"), fmt, fields)
@@ -17,6 +17,7 @@ from scrapebadger_cli.commands import (
17
17
  bing,
18
18
  booking,
19
19
  chatgpt,
20
+ copilot,
20
21
  depop,
21
22
  duckduckgo,
22
23
  ebay,
@@ -115,6 +116,7 @@ app.add_typer(apartments.app, name="apartments", help="Apartments.com rental com
115
116
  app.add_typer(linkedin.app, name="linkedin", help="LinkedIn public data commands")
116
117
  app.add_typer(chatgpt.app, name="chatgpt", help="ChatGPT LLM scraping commands")
117
118
  app.add_typer(perplexity.app, name="perplexity", help="Perplexity AI answer-engine commands")
119
+ app.add_typer(copilot.app, name="copilot", help="Microsoft Copilot answer-engine commands")
118
120
  app.add_typer(gemini.app, name="gemini", help="Google Gemini LLM commands")
119
121
  app.add_typer(
120
122
  facebook.app,
@@ -0,0 +1,42 @@
1
+ from unittest.mock import Mock
2
+
3
+ import pytest
4
+
5
+ CliRunner = pytest.importorskip("typer.testing").CliRunner
6
+ copilot = pytest.importorskip(
7
+ "scrapebadger_cli.commands.copilot", reason="CLI is tested with its own installed package"
8
+ )
9
+
10
+
11
+ def _invoke(monkeypatch, args):
12
+ call = Mock(return_value={})
13
+ monkeypatch.setattr(copilot, "api_get", call)
14
+ monkeypatch.setattr(copilot, "render", Mock())
15
+ result = CliRunner().invoke(copilot.app, args)
16
+ assert result.exit_code == 0, result.output
17
+ return call.call_args.args
18
+
19
+
20
+ def test_ask_leaves_locale_to_the_api_by_default(monkeypatch):
21
+ path, params = _invoke(monkeypatch, ["ask", "q"])
22
+ assert path == "/v1/copilot/ask"
23
+ assert params["country"] is None and params["market"] is None
24
+ assert "strict_country" not in params
25
+
26
+
27
+ def test_ask_country_market_and_strict(monkeypatch):
28
+ _, params = _invoke(
29
+ monkeypatch, ["ask", "q", "-c", "JP", "--market", "ja-JP", "--strict-country"]
30
+ )
31
+ assert params == {"prompt": "q", "country": "JP", "market": "ja-JP", "strict_country": True}
32
+
33
+
34
+ def test_brand(monkeypatch):
35
+ path, params = _invoke(monkeypatch, ["brand", "q", "--brand", "freee", "-c", "JP"])
36
+ assert path == "/v1/copilot/brand-visibility"
37
+ assert params["brand"] == "freee" and params["country"] == "JP"
38
+
39
+
40
+ def test_markets(monkeypatch):
41
+ path, *_ = _invoke(monkeypatch, ["markets"])
42
+ assert path == "/v1/copilot/markets"
@@ -228,7 +228,7 @@ wheels = [
228
228
 
229
229
  [[package]]
230
230
  name = "scrapebadger-cli"
231
- version = "0.25.0"
231
+ version = "0.26.0"
232
232
  source = { editable = "." }
233
233
  dependencies = [
234
234
  { name = "httpx" },