magic-lab-observer-cli 0.1.1__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.
@@ -22,6 +22,9 @@ wheels/
22
22
 
23
23
  # Virtual environments
24
24
  .venv/
25
+ .venv.backup-*/
26
+ .venv.failed-*/
27
+ .runtime/
25
28
  venv/
26
29
  ENV/
27
30
 
@@ -65,4 +68,8 @@ podman-compose.override.yml
65
68
  # OS
66
69
  .DS_Store
67
70
  Thumbs.db
71
+ backend/providers_snapshot.json
68
72
 
73
+ # SQLite WAL side files (the backend enables WAL per connection since 2026-10-17)
74
+ *.db-wal
75
+ *.db-shm
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: magic-lab-observer-cli
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: CLI for LLM Observatory
5
5
  Project-URL: Homepage, https://github.com/Magic-Lab-Studio/observer
6
6
  Project-URL: Repository, https://github.com/Magic-Lab-Studio/observer.git
@@ -13,7 +13,7 @@ console = Console()
13
13
 
14
14
 
15
15
  @click.group()
16
- @click.version_option(version="0.1.1", prog_name="llm-observatory")
16
+ @click.version_option(version="0.2.0", prog_name="llm-observatory")
17
17
  @click.option("--api-key", envvar="OBSERVATORY_API_KEY", default=None, help="API key for backend authentication")
18
18
  @click.pass_context
19
19
  def cli(ctx: click.Context, api_key: str | None) -> None:
@@ -26,7 +26,7 @@ def cli(ctx: click.Context, api_key: str | None) -> None:
26
26
 
27
27
 
28
28
  @cli.command()
29
- @click.option("--db", type=click.Path(exists=True), default="observatory.db", help="SQLite database path")
29
+ @click.option("--db", type=click.Path(exists=True), envvar="OBSERVATORY_DB", default="observatory.db", show_default=True, help="SQLite database path (env OBSERVATORY_DB)")
30
30
  @click.option("--limit", default=20, help="Number of traces to show")
31
31
  @click.option("--status", type=click.Choice(["ok", "error", "unset"]), help="Filter by status")
32
32
  @click.option("--search", help="Search trace names")
@@ -61,7 +61,7 @@ def traces(db: str, limit: int, status: str | None, search: str | None) -> None:
61
61
 
62
62
  @cli.command()
63
63
  @click.argument("trace_id")
64
- @click.option("--db", type=click.Path(exists=True), default="observatory.db", help="SQLite database path")
64
+ @click.option("--db", type=click.Path(exists=True), envvar="OBSERVATORY_DB", default="observatory.db", show_default=True, help="SQLite database path (env OBSERVATORY_DB)")
65
65
  def inspect(trace_id: str, db: str) -> None:
66
66
  """Inspect a trace and its spans."""
67
67
  from cli.db import get_trace, get_trace_spans
@@ -108,7 +108,7 @@ def inspect(trace_id: str, db: str) -> None:
108
108
 
109
109
  @cli.command(name="import")
110
110
  @click.argument("file", type=click.Path(exists=True))
111
- @click.option("--db", type=click.Path(), default="observatory.db", help="SQLite database path")
111
+ @click.option("--db", type=click.Path(), envvar="OBSERVATORY_DB", default="observatory.db", show_default=True, help="SQLite database path (env OBSERVATORY_DB)")
112
112
  @click.option("--dry-run", is_flag=True, help="Preview without writing")
113
113
  def import_data(file: str, db: str, dry_run: bool) -> None:
114
114
  """Import traces from a JSON export file."""
@@ -178,7 +178,7 @@ def import_data(file: str, db: str, dry_run: bool) -> None:
178
178
 
179
179
 
180
180
  @cli.command()
181
- @click.option("--db", type=click.Path(exists=True), default="observatory.db", help="SQLite database path")
181
+ @click.option("--db", type=click.Path(exists=True), envvar="OBSERVATORY_DB", default="observatory.db", show_default=True, help="SQLite database path (env OBSERVATORY_DB)")
182
182
  def stats(db: str) -> None:
183
183
  """Show summary statistics."""
184
184
  from cli.db import get_stats
@@ -199,7 +199,7 @@ def stats(db: str) -> None:
199
199
 
200
200
  @cli.command()
201
201
  @click.argument("trace_id")
202
- @click.option("--db", type=click.Path(exists=True), default="observatory.db", help="SQLite database path")
202
+ @click.option("--db", type=click.Path(exists=True), envvar="OBSERVATORY_DB", default="observatory.db", show_default=True, help="SQLite database path (env OBSERVATORY_DB)")
203
203
  @click.option("--format", "output_format", type=click.Choice(["json", "table"]), default="table")
204
204
  def spans(trace_id: str, db: str, output_format: str) -> None:
205
205
  """List spans for a trace."""
@@ -256,7 +256,7 @@ def spans(trace_id: str, db: str, output_format: str) -> None:
256
256
 
257
257
 
258
258
  @cli.command()
259
- @click.option("--host", default="0.0.0.0", help="Host to bind to")
259
+ @click.option("--host", default="127.0.0.1", help="Host to bind to (loopback by default; see docs/security.md)")
260
260
  @click.option("--port", default=8000, help="Port to bind to")
261
261
  @click.option("--reload", is_flag=True, help="Enable auto-reload")
262
262
  @click.option("--db", default="sqlite+aiosqlite:///./observatory.db", help="Database URL")
@@ -293,7 +293,7 @@ def serve(host: str, port: int, reload: bool, db: str) -> None:
293
293
  help="Evaluator type",
294
294
  )
295
295
  @click.option("--criteria", help="Comma-separated criteria for evaluation")
296
- @click.option("--endpoint", default="http://localhost:8000", help="API endpoint")
296
+ @click.option("--endpoint", envvar="OBSERVER_API_URL", default="http://localhost:8000", show_default=True, help="API endpoint (env OBSERVER_API_URL)")
297
297
  @click.pass_context
298
298
  def evaluate(
299
299
  ctx: click.Context,
@@ -339,7 +339,7 @@ def evaluate(
339
339
 
340
340
 
341
341
  @cli.command()
342
- @click.option("--db", type=click.Path(exists=True), default="observatory.db", help="SQLite database path")
342
+ @click.option("--db", type=click.Path(exists=True), envvar="OBSERVATORY_DB", default="observatory.db", show_default=True, help="SQLite database path (env OBSERVATORY_DB)")
343
343
  @click.option("--format", "output_format", type=click.Choice(["json"]), default="json", help="Export format")
344
344
  @click.option("--output", "-o", type=click.Path(), help="Output file (default: stdout)")
345
345
  @click.option("--status", type=click.Choice(["ok", "error", "unset"]), help="Filter by status")
@@ -374,7 +374,7 @@ def export(db: str, output_format: str, output: str | None, status: str | None,
374
374
 
375
375
  export_data = {
376
376
  "format": "llm-observatory-export",
377
- "version": "0.1.1",
377
+ "version": "0.2.0",
378
378
  "trace_count": len(traces_data),
379
379
  "traces": traces_data,
380
380
  }
@@ -389,7 +389,7 @@ def export(db: str, output_format: str, output: str | None, status: str | None,
389
389
 
390
390
 
391
391
  @cli.command()
392
- @click.option("--endpoint", default="http://localhost:8000", help="API endpoint")
392
+ @click.option("--endpoint", envvar="OBSERVER_API_URL", default="http://localhost:8000", show_default=True, help="API endpoint (env OBSERVER_API_URL)")
393
393
  @click.pass_context
394
394
  def status(ctx: click.Context, endpoint: str) -> None:
395
395
  """Check the status of the observatory server."""
@@ -401,13 +401,22 @@ def status(ctx: click.Context, endpoint: str) -> None:
401
401
  headers["Authorization"] = f"Bearer {api_key}"
402
402
 
403
403
  try:
404
- response = httpx.get(f"{endpoint}/health", headers=headers)
405
- response.raise_for_status()
406
-
404
+ response = httpx.get(f"{endpoint}/health", headers=headers, timeout=5.0)
405
+ except httpx.HTTPError as exc:
406
+ console.print(f"[red]Server is not responding[/red] at {endpoint}: {type(exc).__name__}")
407
+ console.print("Set --endpoint or OBSERVER_API_URL if the server listens elsewhere.")
408
+ raise SystemExit(1) from exc
409
+ try:
410
+ body = response.json()
411
+ except ValueError:
412
+ body = {}
413
+ if response.is_success:
407
414
  console.print("[green]Server is healthy![/green]")
408
- console.print(f"Endpoint: {endpoint}")
409
- except httpx.HTTPError:
410
- console.print("[red]Server is not responding[/red]")
415
+ else:
416
+ console.print(f"[red]Server answered {response.status_code}[/red] {body}")
417
+ console.print(f"Endpoint: {endpoint} db: {body.get('db', '?')} version: {body.get('version', '?')}")
418
+ if not response.is_success:
419
+ raise SystemExit(1)
411
420
 
412
421
 
413
422
  if __name__ == "__main__":
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "magic-lab-observer-cli"
7
- version = "0.1.1"
7
+ version = "0.2.0"
8
8
  description = "CLI for LLM Observatory"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -177,7 +177,7 @@ class TestCLIHelp:
177
177
  def test_version(self, runner: CliRunner) -> None:
178
178
  r = runner.invoke(cli, ["--version"])
179
179
  assert r.exit_code == 0
180
- assert "0.1.1" in r.output
180
+ assert "0.2.0" in r.output
181
181
 
182
182
  def test_help(self, runner: CliRunner) -> None:
183
183
  r = runner.invoke(cli, ["--help"])
@@ -339,7 +339,7 @@ class TestExportCommand:
339
339
  assert r.exit_code == 0
340
340
  data = json.loads(r.output)
341
341
  assert data["format"] == "llm-observatory-export"
342
- assert data["version"] == "0.1.1"
342
+ assert data["version"] == "0.2.0"
343
343
  assert data["trace_count"] == 4
344
344
 
345
345
  def test_export_to_file(self, runner: CliRunner, db_file: Path, tmp_path: Path) -> None:
@@ -408,19 +408,19 @@ class TestEvaluateCommand:
408
408
  class TestStatusCommand:
409
409
  def test_status_server_down(self, runner: CliRunner) -> None:
410
410
  r = runner.invoke(cli, ["status", "--endpoint", "http://127.0.0.1:19999"])
411
- assert r.exit_code == 0
411
+ assert r.exit_code == 1 # a status check that cannot reach the server fails (audit 2026-10-17)
412
412
  assert "not responding" in r.output
413
413
 
414
414
 
415
415
  class TestAPIKeyOption:
416
416
  def test_api_key_from_option(self, runner: CliRunner) -> None:
417
417
  r = runner.invoke(cli, ["--api-key", "test-key", "status", "--endpoint", "http://127.0.0.1:19999"])
418
- assert r.exit_code == 0
418
+ assert r.exit_code == 1
419
419
  assert "not responding" in r.output
420
420
 
421
421
  def test_api_key_none_by_default(self, runner: CliRunner) -> None:
422
422
  r = runner.invoke(cli, ["status", "--endpoint", "http://127.0.0.1:19999"])
423
- assert r.exit_code == 0
423
+ assert r.exit_code == 1
424
424
 
425
425
 
426
426
  class TestImportCommand: