anysite-cli 0.1.0__py3-none-any.whl
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.
Potentially problematic release.
This version of anysite-cli might be problematic. Click here for more details.
- anysite/__init__.py +4 -0
- anysite/__main__.py +6 -0
- anysite/api/__init__.py +21 -0
- anysite/api/client.py +271 -0
- anysite/api/errors.py +137 -0
- anysite/api/schemas.py +333 -0
- anysite/batch/__init__.py +1 -0
- anysite/batch/executor.py +176 -0
- anysite/batch/input.py +160 -0
- anysite/batch/rate_limiter.py +98 -0
- anysite/cli/__init__.py +1 -0
- anysite/cli/config.py +176 -0
- anysite/cli/executor.py +388 -0
- anysite/cli/options.py +249 -0
- anysite/config/__init__.py +11 -0
- anysite/config/paths.py +46 -0
- anysite/config/settings.py +187 -0
- anysite/dataset/__init__.py +37 -0
- anysite/dataset/analyzer.py +268 -0
- anysite/dataset/cli.py +644 -0
- anysite/dataset/collector.py +686 -0
- anysite/dataset/db_loader.py +248 -0
- anysite/dataset/errors.py +30 -0
- anysite/dataset/exporters.py +121 -0
- anysite/dataset/history.py +153 -0
- anysite/dataset/models.py +245 -0
- anysite/dataset/notifications.py +87 -0
- anysite/dataset/scheduler.py +107 -0
- anysite/dataset/storage.py +171 -0
- anysite/dataset/transformer.py +213 -0
- anysite/db/__init__.py +38 -0
- anysite/db/adapters/__init__.py +1 -0
- anysite/db/adapters/base.py +158 -0
- anysite/db/adapters/postgres.py +201 -0
- anysite/db/adapters/sqlite.py +183 -0
- anysite/db/cli.py +687 -0
- anysite/db/config.py +92 -0
- anysite/db/manager.py +166 -0
- anysite/db/operations/__init__.py +1 -0
- anysite/db/operations/insert.py +199 -0
- anysite/db/operations/query.py +43 -0
- anysite/db/schema/__init__.py +1 -0
- anysite/db/schema/inference.py +213 -0
- anysite/db/schema/types.py +71 -0
- anysite/db/utils/__init__.py +1 -0
- anysite/db/utils/sanitize.py +99 -0
- anysite/main.py +498 -0
- anysite/models/__init__.py +1 -0
- anysite/output/__init__.py +11 -0
- anysite/output/console.py +45 -0
- anysite/output/formatters.py +301 -0
- anysite/output/templates.py +76 -0
- anysite/py.typed +0 -0
- anysite/streaming/__init__.py +1 -0
- anysite/streaming/progress.py +121 -0
- anysite/streaming/writer.py +130 -0
- anysite/utils/__init__.py +1 -0
- anysite/utils/fields.py +242 -0
- anysite/utils/retry.py +109 -0
- anysite_cli-0.1.0.dist-info/METADATA +437 -0
- anysite_cli-0.1.0.dist-info/RECORD +64 -0
- anysite_cli-0.1.0.dist-info/WHEEL +4 -0
- anysite_cli-0.1.0.dist-info/entry_points.txt +2 -0
- anysite_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
anysite/main.py
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
"""Main CLI application."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from anysite import __app_name__, __version__
|
|
9
|
+
from anysite.cli import config as config_cli
|
|
10
|
+
|
|
11
|
+
# Create main app
|
|
12
|
+
app = typer.Typer(
|
|
13
|
+
name=__app_name__,
|
|
14
|
+
help="Anysite CLI - Web data extraction for humans and AI agents",
|
|
15
|
+
no_args_is_help=True,
|
|
16
|
+
rich_markup_mode="rich",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Add subcommands
|
|
20
|
+
app.add_typer(config_cli.app, name="config", help="Manage configuration")
|
|
21
|
+
|
|
22
|
+
# Global state for CLI options
|
|
23
|
+
state: dict[str, str | bool | None] = {
|
|
24
|
+
"api_key": None,
|
|
25
|
+
"base_url": None,
|
|
26
|
+
"debug": False,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def version_callback(value: bool) -> None:
|
|
31
|
+
"""Print version and exit."""
|
|
32
|
+
if value:
|
|
33
|
+
console = Console()
|
|
34
|
+
console.print(f"{__app_name__} version: [bold]{__version__}[/bold]")
|
|
35
|
+
raise typer.Exit()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@app.callback()
|
|
39
|
+
def main(
|
|
40
|
+
api_key: Annotated[
|
|
41
|
+
str | None,
|
|
42
|
+
typer.Option(
|
|
43
|
+
"--api-key",
|
|
44
|
+
envvar="ANYSITE_API_KEY",
|
|
45
|
+
help="API key (or set ANYSITE_API_KEY)",
|
|
46
|
+
),
|
|
47
|
+
] = None,
|
|
48
|
+
base_url: Annotated[
|
|
49
|
+
str | None,
|
|
50
|
+
typer.Option(
|
|
51
|
+
"--base-url",
|
|
52
|
+
envvar="ANYSITE_BASE_URL",
|
|
53
|
+
help="API base URL",
|
|
54
|
+
),
|
|
55
|
+
] = None,
|
|
56
|
+
debug: Annotated[
|
|
57
|
+
bool,
|
|
58
|
+
typer.Option(
|
|
59
|
+
"--debug",
|
|
60
|
+
help="Enable debug output",
|
|
61
|
+
),
|
|
62
|
+
] = False,
|
|
63
|
+
no_color: Annotated[
|
|
64
|
+
bool,
|
|
65
|
+
typer.Option(
|
|
66
|
+
"--no-color",
|
|
67
|
+
help="Disable colored output",
|
|
68
|
+
),
|
|
69
|
+
] = False,
|
|
70
|
+
version: Annotated[
|
|
71
|
+
bool | None,
|
|
72
|
+
typer.Option(
|
|
73
|
+
"--version",
|
|
74
|
+
"-v",
|
|
75
|
+
callback=version_callback,
|
|
76
|
+
is_eager=True,
|
|
77
|
+
help="Show version and exit",
|
|
78
|
+
),
|
|
79
|
+
] = None,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Anysite CLI - Web data extraction for humans and AI agents.
|
|
82
|
+
|
|
83
|
+
Get data from LinkedIn, Instagram, Twitter, and more.
|
|
84
|
+
|
|
85
|
+
\b
|
|
86
|
+
Examples:
|
|
87
|
+
anysite linkedin user satyanadella
|
|
88
|
+
anysite linkedin users search --keywords "CTO" --count 10
|
|
89
|
+
anysite instagram user cristiano
|
|
90
|
+
anysite web parse https://example.com
|
|
91
|
+
|
|
92
|
+
\b
|
|
93
|
+
Documentation: https://docs.anysite.io/cli
|
|
94
|
+
"""
|
|
95
|
+
# Store global options
|
|
96
|
+
state["api_key"] = api_key
|
|
97
|
+
state["base_url"] = base_url
|
|
98
|
+
state["debug"] = debug
|
|
99
|
+
|
|
100
|
+
if no_color:
|
|
101
|
+
import os
|
|
102
|
+
|
|
103
|
+
os.environ["NO_COLOR"] = "1"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@app.command("describe")
|
|
107
|
+
def describe(
|
|
108
|
+
command: Annotated[
|
|
109
|
+
str | None,
|
|
110
|
+
typer.Argument(
|
|
111
|
+
help="Endpoint to describe (e.g., 'linkedin.user', '/api/linkedin/user')"
|
|
112
|
+
),
|
|
113
|
+
] = None,
|
|
114
|
+
search: Annotated[
|
|
115
|
+
str | None,
|
|
116
|
+
typer.Option("--search", "-s", help="Search endpoints by keyword"),
|
|
117
|
+
] = None,
|
|
118
|
+
json_output: Annotated[
|
|
119
|
+
bool,
|
|
120
|
+
typer.Option("--json", help="Output as JSON (for agents)"),
|
|
121
|
+
] = False,
|
|
122
|
+
quiet: Annotated[
|
|
123
|
+
bool,
|
|
124
|
+
typer.Option("--quiet", "-q", help="Minimal output (paths only)"),
|
|
125
|
+
] = False,
|
|
126
|
+
) -> None:
|
|
127
|
+
"""Describe API endpoints — input params, output fields, search.
|
|
128
|
+
|
|
129
|
+
Reads from a local cache (run `anysite schema update` first).
|
|
130
|
+
Shows all endpoints from the API, not just those with CLI commands.
|
|
131
|
+
|
|
132
|
+
\b
|
|
133
|
+
Examples:
|
|
134
|
+
anysite describe # list all endpoints
|
|
135
|
+
anysite describe linkedin.user # input + output for endpoint
|
|
136
|
+
anysite describe /api/linkedin/user # full path also works
|
|
137
|
+
anysite describe --search "company" # search by keyword
|
|
138
|
+
anysite describe --json -q # JSON list for agents
|
|
139
|
+
"""
|
|
140
|
+
import json as json_mod
|
|
141
|
+
|
|
142
|
+
from anysite.api.schemas import get_schema, list_endpoints, search_endpoints
|
|
143
|
+
|
|
144
|
+
# Search mode
|
|
145
|
+
if search is not None:
|
|
146
|
+
results = search_endpoints(search)
|
|
147
|
+
if not results:
|
|
148
|
+
typer.echo(f"No endpoints matching '{search}'", err=True)
|
|
149
|
+
typer.echo("Run 'anysite schema update' if cache is empty.", err=True)
|
|
150
|
+
raise typer.Exit(1)
|
|
151
|
+
if json_output:
|
|
152
|
+
typer.echo(json_mod.dumps(results))
|
|
153
|
+
else:
|
|
154
|
+
console = Console()
|
|
155
|
+
console.print(f"[bold]Endpoints matching '{search}':[/bold]\n")
|
|
156
|
+
for r in results:
|
|
157
|
+
console.print(f" {r['path']:<45} [dim]{r['description']}[/dim]")
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
# List all endpoints
|
|
161
|
+
if command is None:
|
|
162
|
+
endpoints = list_endpoints()
|
|
163
|
+
if not endpoints:
|
|
164
|
+
typer.echo("No cached schema. Run: anysite schema update", err=True)
|
|
165
|
+
raise typer.Exit(1)
|
|
166
|
+
if json_output:
|
|
167
|
+
if quiet:
|
|
168
|
+
typer.echo(json_mod.dumps([e["path"] for e in endpoints]))
|
|
169
|
+
else:
|
|
170
|
+
typer.echo(json_mod.dumps(endpoints))
|
|
171
|
+
else:
|
|
172
|
+
console = Console()
|
|
173
|
+
console.print(f"[bold]Available endpoints ({len(endpoints)}):[/bold]\n")
|
|
174
|
+
for ep in endpoints:
|
|
175
|
+
if quiet:
|
|
176
|
+
console.print(f" {ep['path']}")
|
|
177
|
+
else:
|
|
178
|
+
console.print(f" {ep['path']:<45} [dim]{ep['description']}[/dim]")
|
|
179
|
+
console.print(
|
|
180
|
+
"\n[dim]Use 'anysite describe <endpoint>' for details[/dim]"
|
|
181
|
+
)
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
# Describe a specific endpoint
|
|
185
|
+
schema = get_schema(command)
|
|
186
|
+
if schema is None:
|
|
187
|
+
typer.echo(f"Unknown endpoint: {command}", err=True)
|
|
188
|
+
typer.echo("Run 'anysite schema update' to refresh the cache.", err=True)
|
|
189
|
+
raise typer.Exit(1)
|
|
190
|
+
|
|
191
|
+
if json_output:
|
|
192
|
+
typer.echo(json_mod.dumps({"input": schema["input"], "output": schema["output"]}))
|
|
193
|
+
else:
|
|
194
|
+
console = Console()
|
|
195
|
+
desc = schema.get("description", "")
|
|
196
|
+
console.print(f"[bold]{command}[/bold]")
|
|
197
|
+
if desc:
|
|
198
|
+
console.print(f" {desc}\n")
|
|
199
|
+
|
|
200
|
+
input_params = schema.get("input", {})
|
|
201
|
+
if input_params:
|
|
202
|
+
console.print("[bold]Input parameters:[/bold]")
|
|
203
|
+
for name, info in input_params.items():
|
|
204
|
+
req = "[red]*[/red]" if info.get("required") else " "
|
|
205
|
+
desc = info.get("description", "")
|
|
206
|
+
desc_part = f" [dim italic]{desc}[/dim italic]" if desc else ""
|
|
207
|
+
console.print(f" {req} {name:<30} [dim]{info['type']:<10}[/dim]{desc_part}")
|
|
208
|
+
console.print()
|
|
209
|
+
|
|
210
|
+
output_fields = schema.get("output", {})
|
|
211
|
+
if output_fields:
|
|
212
|
+
console.print(f"[bold]Output fields ({len(output_fields)}):[/bold]")
|
|
213
|
+
for name, ftype in output_fields.items():
|
|
214
|
+
console.print(f" {name:<30} [dim]{ftype}[/dim]")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# Schema management subcommand
|
|
218
|
+
schema_app = typer.Typer(help="Manage API schema cache")
|
|
219
|
+
app.add_typer(schema_app, name="schema")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@schema_app.command("update")
|
|
223
|
+
def schema_update() -> None:
|
|
224
|
+
"""Fetch OpenAPI spec and update local schema cache.
|
|
225
|
+
|
|
226
|
+
Downloads the API specification, resolves all references,
|
|
227
|
+
and saves a compact cache to ~/.anysite/schema.json.
|
|
228
|
+
"""
|
|
229
|
+
from anysite.api.schemas import OPENAPI_URL, fetch_and_parse_openapi, save_cache
|
|
230
|
+
|
|
231
|
+
console = Console()
|
|
232
|
+
console.print(f"Fetching OpenAPI spec from {OPENAPI_URL}...")
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
data = fetch_and_parse_openapi()
|
|
236
|
+
except Exception as e:
|
|
237
|
+
console.print(f"[red]Error fetching spec:[/red] {e}")
|
|
238
|
+
raise typer.Exit(1) from e
|
|
239
|
+
|
|
240
|
+
cache_path = save_cache(data)
|
|
241
|
+
count = len(data.get("endpoints", {}))
|
|
242
|
+
console.print(f"[green]✓[/green] Cached {count} endpoints to {cache_path}")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@app.command(
|
|
246
|
+
"api",
|
|
247
|
+
context_settings={"allow_extra_args": True, "allow_interspersed_args": False},
|
|
248
|
+
)
|
|
249
|
+
def api_call(
|
|
250
|
+
ctx: typer.Context,
|
|
251
|
+
endpoint: Annotated[
|
|
252
|
+
str,
|
|
253
|
+
typer.Argument(help="API endpoint path (e.g., /api/linkedin/user)"),
|
|
254
|
+
],
|
|
255
|
+
# Output options
|
|
256
|
+
format: Annotated[
|
|
257
|
+
str,
|
|
258
|
+
typer.Option("--format", "-f", help="Output format (json/jsonl/csv/table)"),
|
|
259
|
+
] = "json",
|
|
260
|
+
fields: Annotated[
|
|
261
|
+
str | None,
|
|
262
|
+
typer.Option("--fields", help="Comma-separated fields to include"),
|
|
263
|
+
] = None,
|
|
264
|
+
output: Annotated[
|
|
265
|
+
str | None,
|
|
266
|
+
typer.Option("--output", "-o", help="Save output to file"),
|
|
267
|
+
] = None,
|
|
268
|
+
quiet: Annotated[
|
|
269
|
+
bool,
|
|
270
|
+
typer.Option("--quiet", "-q", help="Suppress non-data output"),
|
|
271
|
+
] = False,
|
|
272
|
+
exclude: Annotated[
|
|
273
|
+
str | None,
|
|
274
|
+
typer.Option("--exclude", help="Comma-separated fields to exclude"),
|
|
275
|
+
] = None,
|
|
276
|
+
compact: Annotated[
|
|
277
|
+
bool,
|
|
278
|
+
typer.Option("--compact", help="Compact output (no indentation)"),
|
|
279
|
+
] = False,
|
|
280
|
+
# Batch options
|
|
281
|
+
from_file: Annotated[
|
|
282
|
+
str | None,
|
|
283
|
+
typer.Option("--from-file", help="Read inputs from file"),
|
|
284
|
+
] = None,
|
|
285
|
+
stdin: Annotated[
|
|
286
|
+
bool,
|
|
287
|
+
typer.Option("--stdin", help="Read inputs from stdin"),
|
|
288
|
+
] = False,
|
|
289
|
+
parallel: Annotated[
|
|
290
|
+
int,
|
|
291
|
+
typer.Option("--parallel", "-j", help="Number of parallel requests"),
|
|
292
|
+
] = 1,
|
|
293
|
+
delay: Annotated[
|
|
294
|
+
float,
|
|
295
|
+
typer.Option("--delay", help="Delay between requests in seconds"),
|
|
296
|
+
] = 0.0,
|
|
297
|
+
on_error: Annotated[
|
|
298
|
+
str,
|
|
299
|
+
typer.Option("--on-error", help="Error handling: stop, skip, retry"),
|
|
300
|
+
] = "stop",
|
|
301
|
+
rate_limit: Annotated[
|
|
302
|
+
str | None,
|
|
303
|
+
typer.Option("--rate-limit", help="Rate limit (e.g., '10/s', '100/m')"),
|
|
304
|
+
] = None,
|
|
305
|
+
input_key: Annotated[
|
|
306
|
+
str | None,
|
|
307
|
+
typer.Option("--input-key", help="Parameter name to iterate over in batch mode"),
|
|
308
|
+
] = None,
|
|
309
|
+
# Feedback
|
|
310
|
+
progress: Annotated[
|
|
311
|
+
bool | None,
|
|
312
|
+
typer.Option("--progress/--no-progress", help="Show progress bar"),
|
|
313
|
+
] = None,
|
|
314
|
+
stats: Annotated[
|
|
315
|
+
bool,
|
|
316
|
+
typer.Option("--stats", help="Show statistics after completion"),
|
|
317
|
+
] = False,
|
|
318
|
+
verbose: Annotated[
|
|
319
|
+
bool,
|
|
320
|
+
typer.Option("--verbose", help="Verbose output"),
|
|
321
|
+
] = False,
|
|
322
|
+
append: Annotated[
|
|
323
|
+
bool,
|
|
324
|
+
typer.Option("--append", help="Append to existing output file"),
|
|
325
|
+
] = False,
|
|
326
|
+
) -> None:
|
|
327
|
+
"""Call any API endpoint directly with key=value parameters.
|
|
328
|
+
|
|
329
|
+
Pass parameters as key=value pairs after the endpoint path.
|
|
330
|
+
Types are auto-converted using the schema cache.
|
|
331
|
+
|
|
332
|
+
\b
|
|
333
|
+
Examples:
|
|
334
|
+
anysite api /api/linkedin/user user=satyanadella
|
|
335
|
+
anysite api /api/linkedin/search/users title=CTO count=100 --format csv
|
|
336
|
+
anysite api /api/reddit/user user=spez --format table
|
|
337
|
+
anysite api /api/linkedin/user --from-file users.txt --input-key user --parallel 5
|
|
338
|
+
"""
|
|
339
|
+
from pathlib import Path
|
|
340
|
+
|
|
341
|
+
from anysite.api.schemas import convert_value, get_schema
|
|
342
|
+
from anysite.cli.executor import run_search_command, run_single_command
|
|
343
|
+
from anysite.cli.options import ErrorHandling as EH
|
|
344
|
+
from anysite.output.formatters import OutputFormat
|
|
345
|
+
|
|
346
|
+
# Validate endpoint format
|
|
347
|
+
if not endpoint.startswith("/"):
|
|
348
|
+
typer.echo(
|
|
349
|
+
f"Error: endpoint must start with '/' (e.g., /api/linkedin/user), got: {endpoint}",
|
|
350
|
+
err=True,
|
|
351
|
+
)
|
|
352
|
+
raise typer.Exit(1)
|
|
353
|
+
|
|
354
|
+
# Parse key=value args from ctx.args
|
|
355
|
+
raw_params: dict[str, str] = {}
|
|
356
|
+
for arg in ctx.args:
|
|
357
|
+
if "=" not in arg:
|
|
358
|
+
typer.echo(f"Error: invalid parameter '{arg}', expected key=value format", err=True)
|
|
359
|
+
raise typer.Exit(1)
|
|
360
|
+
key, _, value = arg.partition("=")
|
|
361
|
+
raw_params[key] = value
|
|
362
|
+
|
|
363
|
+
# Convert types using schema if available
|
|
364
|
+
schema = get_schema(endpoint)
|
|
365
|
+
payload: dict[str, str | int | bool | float] = {}
|
|
366
|
+
if schema and schema.get("input"):
|
|
367
|
+
input_schema = schema["input"]
|
|
368
|
+
for key, value in raw_params.items():
|
|
369
|
+
if key in input_schema:
|
|
370
|
+
type_hint = input_schema[key].get("type", "string")
|
|
371
|
+
try:
|
|
372
|
+
payload[key] = convert_value(value, type_hint)
|
|
373
|
+
except (ValueError, TypeError):
|
|
374
|
+
payload[key] = value
|
|
375
|
+
else:
|
|
376
|
+
payload[key] = value
|
|
377
|
+
else:
|
|
378
|
+
# No schema — pass as strings, try auto-converting obvious types
|
|
379
|
+
for key, value in raw_params.items():
|
|
380
|
+
if value.isdigit():
|
|
381
|
+
payload[key] = int(value)
|
|
382
|
+
elif value.lower() in ("true", "false"):
|
|
383
|
+
payload[key] = value.lower() == "true"
|
|
384
|
+
else:
|
|
385
|
+
payload[key] = value
|
|
386
|
+
|
|
387
|
+
# Resolve format enum
|
|
388
|
+
try:
|
|
389
|
+
fmt = OutputFormat(format.lower())
|
|
390
|
+
except ValueError:
|
|
391
|
+
typer.echo(f"Error: invalid format '{format}', use json/jsonl/csv/table", err=True)
|
|
392
|
+
raise typer.Exit(1) from None
|
|
393
|
+
|
|
394
|
+
# Resolve error handling enum
|
|
395
|
+
try:
|
|
396
|
+
err_handling = EH(on_error.lower())
|
|
397
|
+
except ValueError:
|
|
398
|
+
typer.echo(f"Error: invalid on-error '{on_error}', use stop/skip/retry", err=True)
|
|
399
|
+
raise typer.Exit(1) from None
|
|
400
|
+
|
|
401
|
+
output_path = Path(output) if output else None
|
|
402
|
+
from_file_path = Path(from_file) if from_file else None
|
|
403
|
+
|
|
404
|
+
is_batch = from_file_path is not None or stdin
|
|
405
|
+
|
|
406
|
+
if is_batch:
|
|
407
|
+
if not input_key and not from_file_path:
|
|
408
|
+
typer.echo(
|
|
409
|
+
"Error: --input-key is required for batch mode with plain text input",
|
|
410
|
+
err=True,
|
|
411
|
+
)
|
|
412
|
+
raise typer.Exit(1)
|
|
413
|
+
|
|
414
|
+
run_single_command(
|
|
415
|
+
endpoint,
|
|
416
|
+
payload,
|
|
417
|
+
format=fmt,
|
|
418
|
+
fields=fields,
|
|
419
|
+
output=output_path,
|
|
420
|
+
quiet=quiet,
|
|
421
|
+
exclude=exclude,
|
|
422
|
+
compact=compact,
|
|
423
|
+
from_file=from_file_path,
|
|
424
|
+
stdin=stdin,
|
|
425
|
+
parallel=parallel,
|
|
426
|
+
delay=delay,
|
|
427
|
+
on_error=err_handling,
|
|
428
|
+
rate_limit=rate_limit,
|
|
429
|
+
progress=progress,
|
|
430
|
+
stats=stats,
|
|
431
|
+
verbose=verbose,
|
|
432
|
+
append=append,
|
|
433
|
+
input_key=input_key or "user",
|
|
434
|
+
extra_payload=dict(payload),
|
|
435
|
+
)
|
|
436
|
+
else:
|
|
437
|
+
run_search_command(
|
|
438
|
+
endpoint,
|
|
439
|
+
dict(payload),
|
|
440
|
+
format=fmt,
|
|
441
|
+
fields=fields,
|
|
442
|
+
output=output_path,
|
|
443
|
+
quiet=quiet,
|
|
444
|
+
exclude=exclude,
|
|
445
|
+
compact=compact,
|
|
446
|
+
stream=False,
|
|
447
|
+
progress=progress,
|
|
448
|
+
stats=stats,
|
|
449
|
+
verbose=verbose,
|
|
450
|
+
append=append,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def get_api_key() -> str | None:
|
|
455
|
+
"""Get API key from global state or settings."""
|
|
456
|
+
if state["api_key"]:
|
|
457
|
+
return str(state["api_key"])
|
|
458
|
+
|
|
459
|
+
from anysite.config import get_settings
|
|
460
|
+
|
|
461
|
+
return get_settings().api_key
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def get_base_url() -> str:
|
|
465
|
+
"""Get base URL from global state or settings."""
|
|
466
|
+
if state["base_url"]:
|
|
467
|
+
return str(state["base_url"])
|
|
468
|
+
|
|
469
|
+
from anysite.config import get_settings
|
|
470
|
+
|
|
471
|
+
return get_settings().base_url
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def is_debug() -> bool:
|
|
475
|
+
"""Check if debug mode is enabled."""
|
|
476
|
+
return bool(state["debug"])
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
# Register dataset subcommand (requires optional [data] dependencies)
|
|
480
|
+
try:
|
|
481
|
+
from anysite.dataset.cli import app as dataset_app
|
|
482
|
+
|
|
483
|
+
app.add_typer(dataset_app, name="dataset", help="Collect, store, and analyze datasets")
|
|
484
|
+
except ImportError:
|
|
485
|
+
pass
|
|
486
|
+
|
|
487
|
+
# Register db subcommand (SQLite always available, PostgreSQL needs optional deps)
|
|
488
|
+
try:
|
|
489
|
+
from anysite.db.cli import app as db_app
|
|
490
|
+
|
|
491
|
+
app.add_typer(db_app, name="db", help="Store API data in SQL databases")
|
|
492
|
+
except ImportError:
|
|
493
|
+
pass
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
if __name__ == "__main__":
|
|
498
|
+
app()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Pydantic models for API responses."""
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Rich console configuration."""
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
|
|
5
|
+
# Main console for standard output
|
|
6
|
+
console = Console()
|
|
7
|
+
|
|
8
|
+
# Error console for stderr
|
|
9
|
+
error_console = Console(stderr=True, style="bold red")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def print_error(message: str) -> None:
|
|
13
|
+
"""Print an error message to stderr.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
message: Error message to print
|
|
17
|
+
"""
|
|
18
|
+
error_console.print(f"[red]✗[/red] {message}")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def print_success(message: str) -> None:
|
|
22
|
+
"""Print a success message.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
message: Success message to print
|
|
26
|
+
"""
|
|
27
|
+
console.print(f"[green]✓[/green] {message}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def print_warning(message: str) -> None:
|
|
31
|
+
"""Print a warning message.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
message: Warning message to print
|
|
35
|
+
"""
|
|
36
|
+
console.print(f"[yellow]![/yellow] {message}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def print_info(message: str) -> None:
|
|
40
|
+
"""Print an info message.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
message: Info message to print
|
|
44
|
+
"""
|
|
45
|
+
console.print(f"[blue]ℹ[/blue] {message}")
|