arionxiv 1.0.32__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.
Files changed (69) hide show
  1. arionxiv/__init__.py +40 -0
  2. arionxiv/__main__.py +10 -0
  3. arionxiv/arxiv_operations/__init__.py +0 -0
  4. arionxiv/arxiv_operations/client.py +225 -0
  5. arionxiv/arxiv_operations/fetcher.py +173 -0
  6. arionxiv/arxiv_operations/searcher.py +122 -0
  7. arionxiv/arxiv_operations/utils.py +293 -0
  8. arionxiv/cli/__init__.py +4 -0
  9. arionxiv/cli/commands/__init__.py +1 -0
  10. arionxiv/cli/commands/analyze.py +587 -0
  11. arionxiv/cli/commands/auth.py +365 -0
  12. arionxiv/cli/commands/chat.py +714 -0
  13. arionxiv/cli/commands/daily.py +482 -0
  14. arionxiv/cli/commands/fetch.py +217 -0
  15. arionxiv/cli/commands/library.py +295 -0
  16. arionxiv/cli/commands/preferences.py +426 -0
  17. arionxiv/cli/commands/search.py +254 -0
  18. arionxiv/cli/commands/settings_unified.py +1407 -0
  19. arionxiv/cli/commands/trending.py +41 -0
  20. arionxiv/cli/commands/welcome.py +168 -0
  21. arionxiv/cli/main.py +407 -0
  22. arionxiv/cli/ui/__init__.py +1 -0
  23. arionxiv/cli/ui/global_theme_manager.py +173 -0
  24. arionxiv/cli/ui/logo.py +127 -0
  25. arionxiv/cli/ui/splash.py +89 -0
  26. arionxiv/cli/ui/theme.py +32 -0
  27. arionxiv/cli/ui/theme_system.py +391 -0
  28. arionxiv/cli/utils/__init__.py +54 -0
  29. arionxiv/cli/utils/animations.py +522 -0
  30. arionxiv/cli/utils/api_client.py +583 -0
  31. arionxiv/cli/utils/api_config.py +505 -0
  32. arionxiv/cli/utils/command_suggestions.py +147 -0
  33. arionxiv/cli/utils/db_config_manager.py +254 -0
  34. arionxiv/github_actions_runner.py +206 -0
  35. arionxiv/main.py +23 -0
  36. arionxiv/prompts/__init__.py +9 -0
  37. arionxiv/prompts/prompts.py +247 -0
  38. arionxiv/rag_techniques/__init__.py +8 -0
  39. arionxiv/rag_techniques/basic_rag.py +1531 -0
  40. arionxiv/scheduler_daemon.py +139 -0
  41. arionxiv/server.py +1000 -0
  42. arionxiv/server_main.py +24 -0
  43. arionxiv/services/__init__.py +73 -0
  44. arionxiv/services/llm_client.py +30 -0
  45. arionxiv/services/llm_inference/__init__.py +58 -0
  46. arionxiv/services/llm_inference/groq_client.py +469 -0
  47. arionxiv/services/llm_inference/llm_utils.py +250 -0
  48. arionxiv/services/llm_inference/openrouter_client.py +564 -0
  49. arionxiv/services/unified_analysis_service.py +872 -0
  50. arionxiv/services/unified_auth_service.py +457 -0
  51. arionxiv/services/unified_config_service.py +456 -0
  52. arionxiv/services/unified_daily_dose_service.py +823 -0
  53. arionxiv/services/unified_database_service.py +1633 -0
  54. arionxiv/services/unified_llm_service.py +366 -0
  55. arionxiv/services/unified_paper_service.py +604 -0
  56. arionxiv/services/unified_pdf_service.py +522 -0
  57. arionxiv/services/unified_prompt_service.py +344 -0
  58. arionxiv/services/unified_scheduler_service.py +589 -0
  59. arionxiv/services/unified_user_service.py +954 -0
  60. arionxiv/utils/__init__.py +51 -0
  61. arionxiv/utils/api_helpers.py +200 -0
  62. arionxiv/utils/file_cleanup.py +150 -0
  63. arionxiv/utils/ip_helper.py +96 -0
  64. arionxiv-1.0.32.dist-info/METADATA +336 -0
  65. arionxiv-1.0.32.dist-info/RECORD +69 -0
  66. arionxiv-1.0.32.dist-info/WHEEL +5 -0
  67. arionxiv-1.0.32.dist-info/entry_points.txt +4 -0
  68. arionxiv-1.0.32.dist-info/licenses/LICENSE +21 -0
  69. arionxiv-1.0.32.dist-info/top_level.txt +1 -0
@@ -0,0 +1,41 @@
1
+ """Trending command for ArionXiv CLI"""
2
+
3
+ import logging
4
+ import click
5
+ from rich.console import Console
6
+ from ..utils.command_suggestions import show_command_suggestions
7
+ from ..ui.theme import get_theme_colors
8
+
9
+ console = Console()
10
+ logger = logging.getLogger(__name__)
11
+
12
+ @click.command()
13
+ @click.option('--category', '-c', help='Filter by category')
14
+ @click.option('--days', '-d', default=7, help='Time period in days (default: 7)')
15
+ @click.option('--limit', '-l', default=20, help='Number of papers to show')
16
+ def trending_command(category: str, days: int, limit: int):
17
+ """
18
+ Discover trending research papers
19
+
20
+ Examples:
21
+ \b
22
+ arionxiv trending
23
+ arionxiv trending --category cs.AI --days 30
24
+ arionxiv trending --limit 50
25
+ """
26
+ logger.info(f"Fetching trending papers: category={category}, days={days}, limit={limit}")
27
+ colors = get_theme_colors()
28
+ primary = colors['primary']
29
+
30
+ console.print("[green]Finding trending papers[/green]")
31
+ if category:
32
+ console.print(f"[{primary}]Category:[/{primary}] {category}")
33
+ console.print(f"[{primary}]Time period:[/{primary}] {days} days")
34
+ console.print(f"[{primary}]Limit:[/{primary}] {limit} papers")
35
+
36
+ # TODO: Implement trending functionality
37
+ logger.debug("Trending feature not yet implemented")
38
+ console.print("[yellow]Feature coming soon![/yellow]")
39
+
40
+ # Show command suggestions
41
+ show_command_suggestions(console, context='trending')
@@ -0,0 +1,168 @@
1
+ """
2
+ ArionXiv Welcome Dashboard - Unified logo and feature showcase
3
+ Clean interface without emojis for professional presentation
4
+ """
5
+
6
+ import click
7
+ import time
8
+ from rich.panel import Panel
9
+ from rich.table import Table
10
+ from rich.columns import Columns
11
+ from rich.text import Text
12
+
13
+ from ..ui.theme import create_themed_console, get_theme_colors
14
+ from ..ui.logo import get_ascii_logo
15
+ from ..utils.animations import slam_content, slam_columns, left_to_right_reveal
16
+
17
+ console = create_themed_console()
18
+
19
+
20
+ def show_logo_and_features(console_instance=None, animate: bool = True):
21
+ """Unified function to show logo and features - used by CLI main and welcome command
22
+
23
+ Args:
24
+ console_instance: Rich console instance
25
+ animate: Whether to use animated reveal effects (default True for welcome command)
26
+
27
+ Animation sequence (when animate=True):
28
+ 1. Shake the logo
29
+ 2. Reveal tagline left to right
30
+ 3. Shake feature boxes one by one
31
+ 4. Final text top to bottom
32
+ """
33
+ if console_instance is None:
34
+ console_instance = console
35
+
36
+ colors = get_theme_colors()
37
+ primary_color = colors['primary']
38
+
39
+ logo = get_ascii_logo()
40
+
41
+ if animate:
42
+ # Step 1: SLAM THE LOGO onto screen with impact!
43
+ slam_content(console_instance, logo, style=f"bold {primary_color}", duration=1.0)
44
+
45
+ # Step 2: REVEAL TAGLINE LEFT TO RIGHT
46
+ console_instance.print()
47
+ tagline = "It would not take all night long on the internet anymore"
48
+ left_to_right_reveal(console_instance, tagline, style=f"bold {primary_color}", duration=0.5)
49
+ tagline = "A humble effort to bring down time taken to read academic papers"
50
+ left_to_right_reveal(console_instance, tagline, style=f"bold {primary_color}", duration=0.5)
51
+ print("\n")
52
+ else:
53
+ logo_text = Text(logo, style=f"bold {primary_color}")
54
+ console_instance.print(logo_text)
55
+ console_instance.print(f"\n[bold {primary_color}]It would not take all night long on the internet anymore[/bold {primary_color}]")
56
+ console_instance.print(f"[bold {primary_color}]A humble effort to bring down time taken to read academic papers[/bold {primary_color}]\n\n")
57
+
58
+ # Create feature panels
59
+ panel_width = 50
60
+ panel_height = 15
61
+
62
+ daily_commands = f"""[bold {primary_color}]Personalized Research Feed[/bold {primary_color}]
63
+
64
+ Get AI-curated papers daily based on your:
65
+ - Research categories (cs.AI, cs.LG, etc.)
66
+ - Keywords and interests
67
+ - Preferred authors
68
+ - Relevance scoring
69
+
70
+ [bold {primary_color}]Commands:[/bold {primary_color}]
71
+ [bold {primary_color}]arionxiv daily[/bold {primary_color}] View dashboard
72
+ [bold {primary_color}]arionxiv daily --run[/bold {primary_color}] Generate new dose
73
+ [bold {primary_color}]arionxiv settings daily[/bold {primary_color}] Configure preferences"""
74
+
75
+ chat_commands = f"""[bold {primary_color}]Intelligent Paper Chat[/bold {primary_color}]
76
+
77
+ Chat with research papers using AI:
78
+ - Enter arXiv ID when prompted
79
+ - Ask questions about content
80
+ - Get summaries and insights
81
+ - Context-aware conversations
82
+
83
+ [bold {primary_color}]Commands:[/bold {primary_color}]
84
+ [bold {primary_color}]arionxiv chat[/bold {primary_color}] Start interactive chat"""
85
+
86
+ research_commands = f"""[bold {primary_color}]Research Tools[/bold {primary_color}]
87
+
88
+ Powerful research capabilities:
89
+ - Search arXiv database
90
+ - Analyze content with AI
91
+ - Manage personal library
92
+
93
+ [bold {primary_color}]Commands:[/bold {primary_color}]
94
+ [bold {primary_color}]arionxiv search "query"[/bold {primary_color}] Search papers
95
+ [bold {primary_color}]arionxiv library[/bold {primary_color}] Manage collection"""
96
+
97
+ settings_commands = f"""[bold {primary_color}]Customization[/bold {primary_color}]
98
+
99
+ Personalize your experience:
100
+ - Set research preferences
101
+ - Configure daily dose
102
+ - Choose UI themes
103
+ - Manage user settings
104
+
105
+ [bold {primary_color}]Commands:[/bold {primary_color}]
106
+ [bold {primary_color}]arionxiv settings[/bold {primary_color}] All settings
107
+ [bold {primary_color}]arionxiv settings theme[/bold {primary_color}] Change theme
108
+ [bold {primary_color}]arionxiv preferences[/bold {primary_color}] Set interests"""
109
+
110
+ daily_panel = Panel(daily_commands, title=f"[bold {primary_color}]Daily Dose[/bold {primary_color}]", border_style=f"bold {primary_color}", width=panel_width, height=panel_height)
111
+ chat_panel = Panel(chat_commands, title=f"[bold {primary_color}]PDF & Paper Chat[/bold {primary_color}]", border_style=f"bold {primary_color}", width=panel_width, height=panel_height)
112
+ research_panel = Panel(research_commands, title=f"[bold {primary_color}]Research & Analysis[/bold {primary_color}]", border_style=f"bold {primary_color}", width=panel_width, height=panel_height)
113
+ settings_panel = Panel(settings_commands, title=f"[bold {primary_color}]Settings & Preferences[/bold {primary_color}]", border_style=f"bold {primary_color}", width=panel_width, height=panel_height)
114
+
115
+ # Step 3: SLAM FEATURE BOXES onto screen one by one!
116
+ if animate:
117
+ row1 = Columns([daily_panel, chat_panel], equal=True)
118
+ slam_columns(console_instance, row1, duration=0.5)
119
+
120
+ row2 = Columns([research_panel, settings_panel], equal=True)
121
+ slam_columns(console_instance, row2, duration=0.5)
122
+ else:
123
+ console_instance.print(Columns([daily_panel, chat_panel], equal=True))
124
+ console_instance.print(Columns([research_panel, settings_panel], equal=True))
125
+
126
+ print("\n")
127
+ # Quick start guide
128
+ quick_start_content = f"""
129
+ [bold {primary_color}]Getting Started:[/bold {primary_color}]
130
+ [bold {primary_color}]1. arionxiv register/login[/bold {primary_color}] - Register/login to get started [bold red](CRITICAL)[/bold red]
131
+ [bold {primary_color}]2. arionxiv settings categories[/bold {primary_color}] - Set your research areas
132
+ [bold {primary_color}]3. arionxiv daily --run[/bold {primary_color}] - Generate your first daily dose
133
+ [bold {primary_color}]4. arionxiv chat[/bold {primary_color}] - Chat with a research paper
134
+ [bold {primary_color}]5. arionxiv search "your topic"[/bold {primary_color}] - Find relevant papers"""
135
+
136
+ tips_content = f"""
137
+ [bold {primary_color}]Pro Tips:[/bold {primary_color}]
138
+ - Run [bold {primary_color}]command --help[/bold {primary_color}] for detailed options
139
+ - Configure preferences once for better results
140
+ - Access all settings with [bold {primary_color}]arionxiv settings[/bold {primary_color}]
141
+
142
+ [bold {primary_color}]Ready to explore research? Choose a feature above![/bold {primary_color}]"""
143
+
144
+ # Step 4: FINAL TEXT TOP TO BOTTOM
145
+ if animate:
146
+ quick_start_lines = quick_start_content.strip().split('\n')
147
+ for line in quick_start_lines:
148
+ console_instance.print(line)
149
+ time.sleep(0.2)
150
+ print("\n")
151
+ tips_lines = tips_content.strip().split('\n')
152
+ for line in tips_lines:
153
+ console_instance.print(line)
154
+ time.sleep(0.2)
155
+ else:
156
+ console_instance.print(quick_start_content)
157
+ console_instance.print(tips_content)
158
+
159
+
160
+ @click.command()
161
+ @click.option('--quick', '-q', is_flag=True, help='Skip animations for quick display')
162
+ def welcome(quick: bool):
163
+ """ArionXiv Welcome Dashboard - Explore all features"""
164
+ show_logo_and_features(console, animate=not quick)
165
+
166
+
167
+ if __name__ == "__main__":
168
+ welcome()
arionxiv/cli/main.py ADDED
@@ -0,0 +1,407 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ ArionXiv CLI - Terminal-based research paper analysis tool
4
+ """
5
+
6
+ import sys
7
+ import os
8
+ import asyncio
9
+ import logging
10
+ from pathlib import Path
11
+
12
+ # Initialize quiet logging for better UX
13
+ from ..services.unified_config_service import unified_config_service
14
+ unified_config_service.setup_logging()
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ import click
19
+ from rich.console import Console
20
+ from rich.text import Text
21
+ from rich.panel import Panel
22
+ from rich.columns import Columns
23
+ from rich.align import Align
24
+ from .commands.search import search_command
25
+ from .commands.chat import chat_command
26
+ from .commands.daily import daily_command
27
+ from .commands.library import library_command
28
+ from .commands.trending import trending_command
29
+ from .commands.settings_unified import settings
30
+ from .commands.auth import auth_command, auth_interface, login_command, logout_command, register_command, session_command
31
+ from .commands.welcome import welcome, show_logo_and_features
32
+ from .ui.logo import display_logo, display_welcome_message, display_startup_info
33
+ from .ui.theme import create_themed_console, print_header, style_text, get_theme_colors
34
+ from .utils.db_config_manager import db_config_manager
35
+ from .utils.api_config import api_config_manager, run_first_time_api_setup
36
+ from .ui.theme import run_theme_selection
37
+ from .utils.animations import left_to_right_reveal, animated_help_line
38
+ from ..services.unified_user_service import unified_user_service
39
+
40
+ console = create_themed_console()
41
+
42
+
43
+ def _display_themed_help(ctx, cmd_or_group, is_group=True):
44
+ """Display themed help with ARIONXIV header and animated commands"""
45
+ from .ui.theme import get_theme_colors
46
+ import shutil
47
+ colors = get_theme_colors()
48
+ primary = colors.get('primary', 'cyan')
49
+
50
+ # Get terminal width for the line
51
+ terminal_width = shutil.get_terminal_size().columns
52
+
53
+ # Display ARIONXIV header at top right
54
+ header_text = Text("ARIONXIV", style=f"bold {primary}")
55
+ console.print(Align.right(header_text))
56
+ # Draw horizontal line spanning the entire terminal
57
+ console.rule(style=f"bold {primary}")
58
+ console.print()
59
+
60
+ # Display usage - always use full command path (e.g., "arionxiv settings" not just "settings")
61
+ # Normalize the command path to always start with "arionxiv"
62
+ def normalize_prog_name(path):
63
+ """Normalize command path to use 'arionxiv' as the base"""
64
+ if not path:
65
+ return 'arionxiv'
66
+ # Replace common invocation patterns with just 'arionxiv'
67
+ path = path.replace('python -m arionxiv', 'arionxiv')
68
+ path = path.replace('__main__.py', 'arionxiv')
69
+ if not path.startswith('arionxiv'):
70
+ path = 'arionxiv ' + path
71
+ return path
72
+
73
+ if is_group:
74
+ prog_name = normalize_prog_name(ctx.command_path or ctx.info_name)
75
+ console.print(f"[bold]Usage:[/bold] [bold {primary}]{prog_name}[/bold {primary}] [OPTIONS] [bold {primary}]COMMAND[/bold {primary}] [ARGS]...")
76
+ else:
77
+ prog_name = normalize_prog_name(ctx.command_path or ctx.info_name)
78
+ # Build usage with argument placeholders
79
+ args_str = ""
80
+ for param in cmd_or_group.params:
81
+ if isinstance(param, click.Argument):
82
+ if param.required:
83
+ args_str += f" {param.name.upper()}"
84
+ else:
85
+ args_str += f" [{param.name.upper()}]"
86
+ console.print(f"[bold]Usage:[/bold] [bold {primary}]{prog_name}[/bold {primary}] [OPTIONS]{args_str}")
87
+
88
+ console.print()
89
+
90
+ # Display description (skip for main CLI with minimal docstring)
91
+ help_text = cmd_or_group.help or ""
92
+ # Skip if just the minimal main CLI docstring
93
+ if help_text and help_text.strip() not in ["ArionXiv CLI", ""]:
94
+ for line in help_text.strip().split('\n'):
95
+ # Color commands in quick access section (lines starting with 'arionxiv')
96
+ stripped = line.strip()
97
+ if stripped.startswith('arionxiv '):
98
+ # Split into command and comment
99
+ if '#' in stripped:
100
+ cmd_part, comment_part = stripped.split('#', 1)
101
+ console.print(f" [{primary}]{cmd_part.strip()}[/{primary}] # {comment_part.strip()}")
102
+ else:
103
+ console.print(f" [{primary}]{stripped}[/{primary}]")
104
+ else:
105
+ console.print(f" {stripped}")
106
+ console.print()
107
+
108
+ # Display options
109
+ params = cmd_or_group.params
110
+ options = [p for p in params if isinstance(p, click.Option)]
111
+ if options:
112
+ console.print(Text("Options:", style="bold white"))
113
+
114
+ # Calculate max option length for alignment
115
+ max_opt_len = 0
116
+ opt_data = []
117
+ for param in options:
118
+ opt_names = ', '.join(param.opts)
119
+ if param.secondary_opts:
120
+ opt_names += ', ' + ', '.join(param.secondary_opts)
121
+ opt_help = param.help or ""
122
+ opt_data.append((opt_names, opt_help))
123
+ max_opt_len = max(max_opt_len, len(opt_names))
124
+
125
+ # Add --help to calculation
126
+ max_opt_len = max(max_opt_len, len("--help"))
127
+
128
+ for opt_names, opt_help in opt_data:
129
+ padding = ' ' * (max_opt_len - len(opt_names) + 2)
130
+ animated_help_line(console, opt_names, opt_help, primary, padding, duration=0.5)
131
+
132
+ # Always add --help
133
+ help_opt = "--help"
134
+ padding = ' ' * (max_opt_len - len(help_opt) + 2)
135
+ animated_help_line(console, help_opt, "Show this message and exit.", primary, padding, duration=0.5)
136
+ console.print()
137
+
138
+ # Display commands (for groups only)
139
+ if is_group and hasattr(cmd_or_group, 'list_commands'):
140
+ commands = []
141
+ for subcommand in cmd_or_group.list_commands(ctx):
142
+ cmd = cmd_or_group.get_command(ctx, subcommand)
143
+ if cmd is None or cmd.hidden:
144
+ continue
145
+ help_text = cmd.get_short_help_str(limit=80)
146
+ commands.append((subcommand, help_text))
147
+
148
+ if commands:
149
+ console.print(Text("Commands:", style="bold white"))
150
+ max_len = max(len(cmd) for cmd, _ in commands)
151
+
152
+ for cmd_name, help_text in commands:
153
+ padding = ' ' * (max_len - len(cmd_name) + 2)
154
+ animated_help_line(console, cmd_name, help_text, primary, padding, duration=0.5)
155
+
156
+ console.print()
157
+
158
+
159
+ class ThemedCommand(click.Command):
160
+ """Custom Click command that uses themed help formatting with animations"""
161
+
162
+ def format_help(self, ctx, formatter):
163
+ """Override to use our custom themed help display"""
164
+ _display_themed_help(ctx, self, is_group=False)
165
+
166
+
167
+ class ThemedSubGroup(click.Group):
168
+ """Custom Click subgroup with themed help and error handling for invalid commands"""
169
+
170
+ def format_help(self, ctx, formatter):
171
+ """Override to use our custom themed help display"""
172
+ _display_themed_help(ctx, self, is_group=True)
173
+
174
+ def invoke(self, ctx):
175
+ """Override invoke to catch errors from subcommands"""
176
+ try:
177
+ return super().invoke(ctx)
178
+ except click.UsageError as e:
179
+ self._show_error(e, ctx)
180
+ raise SystemExit(1)
181
+
182
+ def _show_error(self, error, ctx):
183
+ """Display themed error message for invalid subcommands"""
184
+ colors = get_theme_colors()
185
+ error_console = Console()
186
+ error_msg = str(error)
187
+
188
+ # Get command path for better messaging
189
+ cmd_path = ctx.info_name if ctx else "settings"
190
+ logger.debug(f"Showing error for command path: {cmd_path}")
191
+
192
+ error_console.print()
193
+ error_console.print(f"[bold {colors['error']}]⚠ Invalid Command[/bold {colors['error']}]")
194
+ error_console.print(f"[{colors['error']}]{error_msg}[/{colors['error']}]")
195
+ error_console.print()
196
+
197
+ # Show available subcommands
198
+ error_console.print(f"[bold white]Available '{cmd_path}' subcommands:[/bold white]")
199
+ for cmd_name in sorted(self.list_commands(ctx)):
200
+ cmd = self.get_command(ctx, cmd_name)
201
+ if cmd and not cmd.hidden:
202
+ help_text = cmd.get_short_help_str(limit=50)
203
+ error_console.print(f" [{colors['primary']}]{cmd_name}[/{colors['primary']}] {help_text}")
204
+
205
+ error_console.print()
206
+ error_console.print(f"Run [{colors['primary']}]arionxiv {cmd_path} --help[/{colors['primary']}] for more information.")
207
+ error_console.print()
208
+
209
+
210
+ def _patch_command_help(cmd):
211
+ """Patch a command to use themed help display, recursively for groups"""
212
+
213
+ def themed_format_help(ctx, formatter):
214
+ _display_themed_help(ctx, cmd, is_group=isinstance(cmd, click.Group))
215
+
216
+ cmd.format_help = themed_format_help
217
+
218
+ # Recursively patch subcommands if this is a group
219
+ if isinstance(cmd, click.Group):
220
+ for name in cmd.list_commands(None):
221
+ subcmd = cmd.get_command(None, name)
222
+ if subcmd:
223
+ _patch_command_help(subcmd)
224
+
225
+ return cmd
226
+
227
+
228
+ class ThemedGroup(click.Group):
229
+ """Custom Click group that uses themed help formatting with animations"""
230
+
231
+ def format_help(self, ctx, formatter):
232
+ """Override to use our custom themed help display"""
233
+ _display_themed_help(ctx, self, is_group=True)
234
+
235
+ def add_command(self, cmd, name=None):
236
+ """Override to patch commands with themed help"""
237
+ # Patch the command's help formatting
238
+ _patch_command_help(cmd)
239
+ super().add_command(cmd, name)
240
+
241
+ def invoke(self, ctx):
242
+ """Override invoke to catch errors from subcommands"""
243
+ try:
244
+ return super().invoke(ctx)
245
+ except click.UsageError as e:
246
+ self._show_error(e, ctx)
247
+ raise SystemExit(1)
248
+
249
+ def _show_error(self, error, ctx):
250
+ """Display themed error message for invalid commands"""
251
+ import sys
252
+ colors = get_theme_colors()
253
+ error_console = Console()
254
+ error_msg = str(error)
255
+
256
+ # Try to determine which command/subcommand caused the error
257
+ args_list = sys.argv[1:]
258
+
259
+ # Check if this is a subcommand error (e.g., "settings invalidcmd")
260
+ subgroup = None
261
+ parent_cmd = None
262
+ if args_list and len(args_list) >= 1:
263
+ potential_subcmd = args_list[0]
264
+ subcmd = self.get_command(ctx, potential_subcmd)
265
+ if subcmd and isinstance(subcmd, click.Group):
266
+ subgroup = subcmd
267
+ parent_cmd = potential_subcmd
268
+
269
+ error_console.print()
270
+ error_console.print(f"[bold {colors['error']}]⚠ Invalid Command[/bold {colors['error']}]")
271
+ error_console.print(f"[{colors['error']}]{error_msg}[/{colors['error']}]")
272
+ error_console.print()
273
+
274
+ # Show available commands from the relevant group
275
+ if subgroup and parent_cmd:
276
+ # Show subcommands of the subgroup
277
+ logger.debug(f"Showing subcommands for: {parent_cmd}")
278
+ error_console.print(f"[bold white]Available '{parent_cmd}' subcommands:[/bold white]")
279
+ for cmd_name in sorted(subgroup.list_commands(ctx)):
280
+ cmd = subgroup.get_command(ctx, cmd_name)
281
+ if cmd and not cmd.hidden:
282
+ help_text = cmd.get_short_help_str(limit=50)
283
+ error_console.print(f" [{colors['primary']}]{cmd_name}[/{colors['primary']}] {help_text}")
284
+ error_console.print()
285
+ error_console.print(f"Run [{colors['primary']}]arionxiv {parent_cmd} --help[/{colors['primary']}] for more information.")
286
+ else:
287
+ # Show main commands
288
+ logger.debug("Showing main commands")
289
+ error_console.print(f"[bold white]Available commands:[/bold white]")
290
+ for cmd_name in sorted(self.list_commands(ctx)):
291
+ cmd = self.get_command(ctx, cmd_name)
292
+ if cmd and not cmd.hidden:
293
+ help_text = cmd.get_short_help_str(limit=50)
294
+ error_console.print(f" [{colors['primary']}]{cmd_name}[/{colors['primary']}] {help_text}")
295
+ error_console.print()
296
+ error_console.print(f"Run [{colors['primary']}]arionxiv --help[/{colors['primary']}] for more information.")
297
+
298
+ error_console.print()
299
+
300
+ def main(self, *args, standalone_mode=True, **kwargs):
301
+ """Override main to intercept --help and handle invalid commands"""
302
+ import sys
303
+ logger.debug(f"CLI invoked with args: {sys.argv[1:]}")
304
+ # Only intercept if --help is the first or second argument (for main group help)
305
+ # Don't intercept if there's a subcommand before --help
306
+ args_list = sys.argv[1:]
307
+
308
+ # Check if --help is for the main group (not a subcommand)
309
+ if args_list and args_list[0] in ('--help', '-h'):
310
+ try:
311
+ with self.make_context('arionxiv', []) as ctx:
312
+ _display_themed_help(ctx, self, is_group=True)
313
+ sys.exit(0)
314
+ except click.exceptions.Exit:
315
+ sys.exit(0)
316
+ except Exception:
317
+ pass
318
+
319
+ # Use standalone_mode=False to handle exceptions ourselves
320
+ try:
321
+ return super().main(*args, standalone_mode=False, **kwargs)
322
+ except click.UsageError as e:
323
+ self._show_error(e, None)
324
+ sys.exit(1)
325
+ except click.Abort:
326
+ sys.exit(1)
327
+ except click.exceptions.Exit as e:
328
+ sys.exit(e.exit_code)
329
+
330
+
331
+ @click.group(cls=ThemedGroup, invoke_without_command=True)
332
+ @click.option('--version', is_flag=True, help='Show version information')
333
+ @click.pass_context
334
+ def cli(ctx, version):
335
+ """ArionXiv CLI"""
336
+ if version:
337
+ console.print(style_text("ArionXiv CLI v1.0.0", "success"))
338
+ return
339
+
340
+ # Load stored API keys into environment
341
+ api_config_manager.load_keys_to_environment()
342
+
343
+ # Check for first-time API setup (only when no subcommand)
344
+ if ctx.invoked_subcommand is None:
345
+ if api_config_manager.is_first_time_setup_needed():
346
+ # Show logo first
347
+ show_logo_and_features(console, animate=False)
348
+ # Run first-time API setup
349
+ run_first_time_api_setup(console)
350
+ console.print()
351
+ else:
352
+ # Show unified logo and features for any user
353
+ show_logo_and_features(console, animate=False)
354
+
355
+ # Show a simple getting started message
356
+ colors = get_theme_colors()
357
+ console.print(f"[bold {colors['primary']}]Ready to explore research papers? Try:[/bold {colors['primary']}]")
358
+ console.print(f" [bold {colors['primary']}]arionxiv search \"your topic\"[/bold {colors['primary']}]")
359
+ console.print()
360
+
361
+ async def _handle_main_flow():
362
+ """Handle the main CLI flow with authentication"""
363
+ # Ensure user is authenticated
364
+ user = await auth_interface.ensure_authenticated()
365
+ if not user:
366
+ return
367
+
368
+ # Load configuration
369
+ await db_config_manager.load_config()
370
+
371
+ # Check if first time user or theme not configured
372
+ is_first_time = db_config_manager.get('first_time_user', True)
373
+ theme_configured = db_config_manager.is_theme_configured()
374
+
375
+ # If first time or theme not configured, run theme selection
376
+ if is_first_time or not theme_configured:
377
+ selected_theme = run_theme_selection(console)
378
+ await db_config_manager.set_theme_color(selected_theme)
379
+
380
+ # Always show logo (now with selected theme)
381
+ display_logo(console)
382
+
383
+ if is_first_time:
384
+ display_welcome_message(console)
385
+ # Mark as not first time user
386
+ await db_config_manager.set('first_time_user', False)
387
+ else:
388
+ display_startup_info(console)
389
+
390
+ console.print(f"Use {style_text('arionxiv --help', 'primary')} to see available commands")
391
+
392
+ # Register all commands
393
+ cli.add_command(welcome, name="welcome")
394
+ cli.add_command(search_command, name="search")
395
+ cli.add_command(chat_command, name="chat")
396
+ cli.add_command(daily_command, name="daily")
397
+ cli.add_command(library_command, name="library")
398
+ cli.add_command(trending_command, name="trending")
399
+ cli.add_command(settings, name="settings")
400
+ cli.add_command(login_command, name="login")
401
+ cli.add_command(logout_command, name="logout")
402
+ cli.add_command(register_command, name="register")
403
+ cli.add_command(session_command, name="session")
404
+ cli.add_command(auth_command, name="auth") # Hidden, for backward compatibility
405
+
406
+ if __name__ == "__main__":
407
+ cli()
@@ -0,0 +1 @@
1
+ """UI Package"""