lean-explore 0.1.1__tar.gz → 0.1.2__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 (32) hide show
  1. {lean_explore-0.1.1 → lean_explore-0.1.2}/PKG-INFO +1 -1
  2. {lean_explore-0.1.1 → lean_explore-0.1.2}/pyproject.toml +1 -1
  3. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/cli/main.py +89 -57
  4. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore.egg-info/PKG-INFO +1 -1
  5. {lean_explore-0.1.1 → lean_explore-0.1.2}/LICENSE +0 -0
  6. {lean_explore-0.1.1 → lean_explore-0.1.2}/README.md +0 -0
  7. {lean_explore-0.1.1 → lean_explore-0.1.2}/setup.cfg +0 -0
  8. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/__init__.py +0 -0
  9. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/api/__init__.py +0 -0
  10. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/api/client.py +0 -0
  11. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/cli/__init__.py +0 -0
  12. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/cli/agent.py +0 -0
  13. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/cli/config_utils.py +0 -0
  14. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/cli/data_commands.py +0 -0
  15. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/defaults.py +0 -0
  16. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/local/__init__.py +0 -0
  17. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/local/search.py +0 -0
  18. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/local/service.py +0 -0
  19. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/mcp/__init__.py +0 -0
  20. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/mcp/app.py +0 -0
  21. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/mcp/server.py +0 -0
  22. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/mcp/tools.py +0 -0
  23. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/shared/__init__.py +0 -0
  24. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/shared/models/__init__.py +0 -0
  25. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/shared/models/api.py +0 -0
  26. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore/shared/models/db.py +0 -0
  27. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore.egg-info/SOURCES.txt +0 -0
  28. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore.egg-info/dependency_links.txt +0 -0
  29. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore.egg-info/entry_points.txt +0 -0
  30. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore.egg-info/requires.txt +0 -0
  31. {lean_explore-0.1.1 → lean_explore-0.1.2}/src/lean_explore.egg-info/top_level.txt +0 -0
  32. {lean_explore-0.1.1 → lean_explore-0.1.2}/tests/test_defaults.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-explore
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: A project to explore and rank Lean mathematical declarations.
5
5
  Author-email: Justin Asher <justinchadwickasher@gmail.com>
6
6
  License: Apache License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "lean-explore"
7
- version = "0.1.1"
7
+ version = "0.1.2"
8
8
  authors = [
9
9
  { name = "Justin Asher", email = "justinchadwickasher@gmail.com" },
10
10
  ]
@@ -60,6 +60,7 @@ app.command("chat", help="Interact with an AI agent using Lean Explore tools.")(
60
60
 
61
61
 
62
62
  console = Console()
63
+ error_console = Console(stderr=True)
63
64
 
64
65
  # Content width for panels.
65
66
  PANEL_CONTENT_WIDTH = 80
@@ -75,9 +76,15 @@ def configure_lean_explore_api_key(
75
76
  confirmation_prompt=True,
76
77
  ),
77
78
  ):
78
- """Configure and save your Lean Explore API key."""
79
+ """Configure and save your Lean Explore API key.
80
+
81
+ Args:
82
+ api_key: The API key string to save. Prompts if not provided.
83
+ """
79
84
  if not api_key:
80
- console.print("[bold red]Lean Explore API key cannot be empty.[/bold red]")
85
+ error_console.print(
86
+ "[bold red]Lean Explore API key cannot be empty.[/bold red]"
87
+ )
81
88
  raise typer.Abort()
82
89
 
83
90
  if config_utils.save_api_key(api_key):
@@ -87,7 +94,7 @@ def configure_lean_explore_api_key(
87
94
  f"{config_path}[/bold green]"
88
95
  )
89
96
  else:
90
- console.print(
97
+ error_console.print(
91
98
  "[bold red]Failed to save Lean Explore API key. "
92
99
  "Check logs or permissions.[/bold red]"
93
100
  )
@@ -107,9 +114,12 @@ def configure_openai_api_key(
107
114
  """Configure and save your OpenAI API key.
108
115
 
109
116
  This key is used by agent functionalities that leverage OpenAI models.
117
+
118
+ Args:
119
+ api_key: The OpenAI API key string to save. Prompts if not provided.
110
120
  """
111
121
  if not api_key:
112
- console.print("[bold red]OpenAI API key cannot be empty.[/bold red]")
122
+ error_console.print("[bold red]OpenAI API key cannot be empty.[/bold red]")
113
123
  raise typer.Abort()
114
124
 
115
125
  if config_utils.save_openai_api_key(api_key):
@@ -119,7 +129,7 @@ def configure_openai_api_key(
119
129
  f"{config_path}[/bold green]"
120
130
  )
121
131
  else:
122
- console.print(
132
+ error_console.print(
123
133
  "[bold red]Failed to save OpenAI API key. "
124
134
  "Check logs or permissions.[/bold red]"
125
135
  )
@@ -130,12 +140,12 @@ def _get_api_client() -> Optional[APIClient]:
130
140
  """Loads Lean Explore API key and initializes the APIClient.
131
141
 
132
142
  Returns:
133
- APIClient instance if key is found, None otherwise.
143
+ Optional[APIClient]: APIClient instance if key is found, None otherwise.
134
144
  """
135
145
  api_key = config_utils.load_api_key()
136
146
  if not api_key:
137
147
  config_path = config_utils.get_config_file_path()
138
- console.print(
148
+ error_console.print(
139
149
  "[bold yellow]Lean Explore API key not configured. Please run:"
140
150
  "[/bold yellow]\n"
141
151
  f" `leanexplore configure api-key`\n"
@@ -146,7 +156,15 @@ def _get_api_client() -> Optional[APIClient]:
146
156
 
147
157
 
148
158
  def _format_text_for_fixed_panel(text_content: Optional[str], width: int) -> str:
149
- """Wraps text and pads lines to ensure fixed content width for a Panel."""
159
+ """Wraps text and pads lines to ensure fixed content width for a Panel.
160
+
161
+ Args:
162
+ text_content: The text content to wrap and pad.
163
+ width: The target width for text wrapping and padding.
164
+
165
+ Returns:
166
+ A string with wrapped and padded text suitable for fixed-width display.
167
+ """
150
168
  if not text_content:
151
169
  return " " * width
152
170
 
@@ -188,16 +206,23 @@ def _format_text_for_fixed_panel(text_content: Optional[str], width: int) -> str
188
206
  if i < len(paragraphs) - 1 and (
189
207
  paragraph.strip() or (not paragraph.strip() and not lines_in_paragraph)
190
208
  ):
209
+ # Add a blank padded line between paragraphs
191
210
  final_output_lines.append(" " * width)
192
211
 
193
212
  if not final_output_lines and text_content.strip():
213
+ # Fallback for content that becomes empty after processing but was not initially
194
214
  return " " * width
195
215
 
196
216
  return "\n".join(final_output_lines)
197
217
 
198
218
 
199
219
  def _display_search_results(response: APISearchResponse, display_limit: int = 5):
200
- """Displays search results using fixed-width Panels for each item."""
220
+ """Displays search results using fixed-width Panels for each item.
221
+
222
+ Args:
223
+ response: The APISearchResponse object from the backend.
224
+ display_limit: The maximum number of individual results to display in detail.
225
+ """
201
226
  console.print(
202
227
  Panel(
203
228
  f"[bold cyan]Search Query:[/bold cyan] {response.query}",
@@ -222,7 +247,7 @@ def _display_search_results(response: APISearchResponse, display_limit: int = 5)
222
247
  console.print("[yellow]No results found.[/yellow]")
223
248
  return
224
249
 
225
- console.print("")
250
+ console.print("") # Adds a blank line for spacing
226
251
 
227
252
  for i, item in enumerate(response.results):
228
253
  if i >= display_limit:
@@ -288,7 +313,7 @@ def _display_search_results(response: APISearchResponse, display_limit: int = 5)
288
313
  "code) available for this item.[/dim]"
289
314
  )
290
315
 
291
- if i < num_results_to_show - 1:
316
+ if i < num_results_to_show - 1: # Add spacing between items
292
317
  console.print("")
293
318
 
294
319
  console.rule(style="dim")
@@ -297,9 +322,12 @@ def _display_search_results(response: APISearchResponse, display_limit: int = 5)
297
322
  f"...and {len(response.results) - num_results_to_show} more results "
298
323
  "received from server but not shown due to limit."
299
324
  )
300
- elif response.count > len(response.results):
325
+ elif response.count > len(
326
+ response.results
327
+ ): # Should be total_candidates_considered
301
328
  console.print(
302
- f"...and {response.count - len(response.results)} more results available "
329
+ f"...and {response.total_candidates_considered - len(response.results)} "
330
+ "more results available "
303
331
  "on server."
304
332
  )
305
333
 
@@ -318,7 +346,13 @@ async def search_command(
318
346
  5, "--limit", "-n", help="Number of search results to display."
319
347
  ),
320
348
  ):
321
- """Search for Lean statement groups using the Lean Explore API."""
349
+ """Search for Lean statement groups using the Lean Explore API.
350
+
351
+ Args:
352
+ query_string: The natural language query to search for.
353
+ package: An optional list of package names to filter results by.
354
+ limit: The maximum number of search results to display to the user.
355
+ """
322
356
  client = _get_api_client()
323
357
  if not client:
324
358
  raise typer.Exit(code=1)
@@ -329,33 +363,33 @@ async def search_command(
329
363
  _display_search_results(response, display_limit=limit)
330
364
  except httpx.HTTPStatusError as e:
331
365
  if e.response.status_code == 401:
332
- console.print(
366
+ error_console.print(
333
367
  f"[bold red]API Error {e.response.status_code}: Unauthorized. "
334
368
  "Your API key might be invalid or expired.[/bold red]"
335
369
  )
336
- console.print(
370
+ error_console.print(
337
371
  "Please reconfigure your API key using: `leanexplore configure api-key`"
338
372
  )
339
373
  else:
340
374
  try:
341
375
  error_detail = e.response.json().get("detail", e.response.text)
342
- console.print(
376
+ error_console.print(
343
377
  f"[bold red]API Error {e.response.status_code}: "
344
378
  f"{error_detail}[/bold red]"
345
379
  )
346
380
  except Exception:
347
- console.print(
381
+ error_console.print(
348
382
  f"[bold red]API Error {e.response.status_code}: "
349
383
  f"{e.response.text}[/bold red]"
350
384
  )
351
385
  raise typer.Exit(code=1)
352
386
  except httpx.RequestError as e:
353
- console.print(
387
+ error_console.print(
354
388
  f"[bold red]Network Error: Could not connect to the API. {e}[/bold red]"
355
389
  )
356
390
  raise typer.Exit(code=1)
357
391
  except Exception as e:
358
- console.print(f"[bold red]An unexpected error occurred: {e}[/bold red]")
392
+ error_console.print(f"[bold red]An unexpected error occurred: {e}[/bold red]")
359
393
  raise typer.Exit(code=1)
360
394
 
361
395
 
@@ -366,7 +400,11 @@ async def get_by_id_command(
366
400
  ..., help="The ID of the statement group to retrieve."
367
401
  ),
368
402
  ):
369
- """Get detailed information about a specific statement group by its ID."""
403
+ """Get detailed information about a specific statement group by its ID.
404
+
405
+ Args:
406
+ group_id: The unique integer identifier of the statement group.
407
+ """
370
408
  client = _get_api_client()
371
409
  if not client:
372
410
  raise typer.Exit(code=1)
@@ -437,36 +475,36 @@ async def get_by_id_command(
437
475
  )
438
476
 
439
477
  else:
440
- console.print(
478
+ error_console.print( # Changed to error_console for error/warning message
441
479
  f"[yellow]Statement group with ID {group_id} not found.[/yellow]"
442
480
  )
443
481
 
444
482
  except httpx.HTTPStatusError as e:
445
483
  if e.response.status_code == 401:
446
- console.print(
484
+ error_console.print(
447
485
  f"[bold red]API Error {e.response.status_code}: Unauthorized."
448
486
  " Your API key might be invalid or expired.[/bold red]"
449
487
  )
450
488
  else:
451
489
  try:
452
490
  error_detail = e.response.json().get("detail", e.response.text)
453
- console.print(
491
+ error_console.print(
454
492
  f"[bold red]API Error {e.response.status_code}: "
455
493
  f"{error_detail}[/bold red]"
456
494
  )
457
495
  except Exception:
458
- console.print(
496
+ error_console.print(
459
497
  f"[bold red]API Error {e.response.status_code}: "
460
498
  f"{e.response.text}[/bold red]"
461
499
  )
462
500
  raise typer.Exit(code=1)
463
501
  except httpx.RequestError as e:
464
- console.print(
502
+ error_console.print(
465
503
  f"[bold red]Network Error: Could not connect to the API. {e}[/bold red]"
466
504
  )
467
505
  raise typer.Exit(code=1)
468
506
  except Exception as e:
469
- console.print(f"[bold red]An unexpected error occurred: {e}[/bold red]")
507
+ error_console.print(f"[bold red]An unexpected error occurred: {e}[/bold red]")
470
508
  raise typer.Exit(code=1)
471
509
 
472
510
 
@@ -477,7 +515,11 @@ async def get_dependencies_command(
477
515
  ..., help="The ID of the statement group to get dependencies for."
478
516
  ),
479
517
  ):
480
- """Get dependencies (citations) for a specific statement group by its ID."""
518
+ """Get dependencies (citations) for a specific statement group by its ID.
519
+
520
+ Args:
521
+ group_id: The unique integer identifier of the statement group.
522
+ """
481
523
  client = _get_api_client()
482
524
  if not client:
483
525
  raise typer.Exit(code=1)
@@ -519,44 +561,40 @@ async def get_dependencies_command(
519
561
  else:
520
562
  console.print("[yellow]No citations found for this group.[/yellow]")
521
563
  else:
522
- console.print(
564
+ error_console.print( # Changed to error_console for error/warning message
523
565
  f"[yellow]Statement group with ID {group_id} not found or no "
524
566
  "citations data available.[/yellow]"
525
567
  )
526
568
 
527
569
  except httpx.HTTPStatusError as e:
528
570
  if e.response.status_code == 401:
529
- console.print(
571
+ error_console.print(
530
572
  f"[bold red]API Error {e.response.status_code}: Unauthorized."
531
573
  " Your API key might be invalid or expired.[/bold red]"
532
574
  )
533
575
  else:
534
576
  try:
535
577
  error_detail = e.response.json().get("detail", e.response.text)
536
- console.print(
578
+ error_console.print(
537
579
  f"[bold red]API Error {e.response.status_code}: "
538
580
  f"{error_detail}[/bold red]"
539
581
  )
540
582
  except Exception:
541
- console.print(
583
+ error_console.print(
542
584
  f"[bold red]API Error {e.response.status_code}: "
543
585
  f"{e.response.text}[/bold red]"
544
586
  )
545
587
  raise typer.Exit(code=1)
546
588
  except httpx.RequestError as e:
547
- console.print(
589
+ error_console.print(
548
590
  f"[bold red]Network Error: Could not connect to the API. {e}[/bold red]"
549
591
  )
550
592
  raise typer.Exit(code=1)
551
593
  except Exception as e:
552
- console.print(f"[bold red]An unexpected error occurred: {e}[/bold red]")
594
+ error_console.print(f"[bold red]An unexpected error occurred: {e}[/bold red]")
553
595
  raise typer.Exit(code=1)
554
596
 
555
597
 
556
- # The placeholder download-db command is removed as its functionality
557
- # will be covered by the `leanexplore data fetch` command.
558
-
559
-
560
598
  @mcp_app.command("serve")
561
599
  def mcp_serve_command(
562
600
  backend: str = typer.Option(
@@ -579,6 +617,10 @@ def mcp_serve_command(
579
617
  The server communicates via stdio and provides Lean search functionalities
580
618
  as MCP tools. The actual checks for local data presence or API key validity
581
619
  are handled by the 'lean_explore.mcp.server' module when it starts.
620
+
621
+ Args:
622
+ backend: The backend choice ('api' or 'local').
623
+ api_key_override: Optional API key to override any stored key.
582
624
  """
583
625
  command_parts = [
584
626
  sys.executable,
@@ -589,41 +631,33 @@ def mcp_serve_command(
589
631
  ]
590
632
 
591
633
  if backend.lower() == "api":
592
- # API key will be loaded by mcp.server or passed if overridden
593
- # We still check here to provide immediate feedback if --api-key
594
- # is needed but not stored.
595
634
  effective_lean_explore_api_key = api_key_override or config_utils.load_api_key()
596
635
  if not effective_lean_explore_api_key:
597
- console.print(
636
+ error_console.print(
598
637
  "[bold red]Lean Explore API key is required for 'api' backend."
599
638
  "[/bold red]\n"
600
639
  "Please configure it using `leanexplore configure api-key` "
601
640
  "or provide it with the `--api-key` option for this command."
602
641
  )
603
642
  raise typer.Abort()
604
- # Pass the override if provided; otherwise, mcp.server will load it.
605
643
  if api_key_override:
606
644
  command_parts.extend(["--api-key", api_key_override])
607
645
  elif backend.lower() == "local":
608
- # The mcp.server module will now handle checks for local data existence
609
- # and provide user guidance. No explicit checks needed here in main.py.
610
- console.print(
646
+ error_console.print( # Changed to error_console for consistency
611
647
  "[dim]Attempting to start MCP server with 'local' backend. "
612
648
  "The server will verify local data availability.[/dim]"
613
649
  )
614
650
  else:
615
- # This case should ideally not be reached due to Typer's choice validation,
616
- # but as a safeguard.
617
- console.print(
651
+ error_console.print(
618
652
  f"[bold red]Invalid backend: '{backend}'. Must be 'api' or 'local'."
619
653
  "[/bold red]"
620
654
  )
621
655
  raise typer.Abort()
622
656
 
623
- console.print(
657
+ error_console.print(
624
658
  f"[green]Launching MCP server subprocess with '{backend}' backend...[/green]"
625
659
  )
626
- console.print(
660
+ error_console.print(
627
661
  "[dim]The server will now take over stdio. To stop it, the connected MCP "
628
662
  "client should disconnect, or you may need to manually terminate this process "
629
663
  "(e.g., Ctrl+C if no client is managing it).[/dim]"
@@ -632,24 +666,22 @@ def mcp_serve_command(
632
666
  try:
633
667
  process_result = subprocess.run(command_parts, check=False)
634
668
  if process_result.returncode != 0:
635
- # mcp.server should have printed its own detailed error message
636
- # before exiting with a non-zero code.
637
- console.print(
669
+ error_console.print(
638
670
  f"[bold red]MCP server subprocess exited with code: "
639
671
  f"{process_result.returncode}. Check server logs above for "
640
672
  f"details.[/bold red]"
641
673
  )
642
674
  except FileNotFoundError:
643
- console.print(
675
+ error_console.print(
644
676
  f"[bold red]Error: Could not find Python interpreter '{sys.executable}' "
645
677
  f"or the MCP server module 'lean_explore.mcp.server'.[/bold red]"
646
678
  )
647
- console.print(
679
+ error_console.print(
648
680
  "Please ensure the package is installed correctly and "
649
681
  "`python -m lean_explore.mcp.server` is runnable."
650
682
  )
651
683
  except Exception as e:
652
- console.print(
684
+ error_console.print(
653
685
  f"[bold red]An error occurred while trying to launch or run the MCP "
654
686
  f"server: {e}[/bold red]"
655
687
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-explore
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: A project to explore and rank Lean mathematical declarations.
5
5
  Author-email: Justin Asher <justinchadwickasher@gmail.com>
6
6
  License: Apache License
File without changes
File without changes
File without changes