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.
- arionxiv/__init__.py +40 -0
- arionxiv/__main__.py +10 -0
- arionxiv/arxiv_operations/__init__.py +0 -0
- arionxiv/arxiv_operations/client.py +225 -0
- arionxiv/arxiv_operations/fetcher.py +173 -0
- arionxiv/arxiv_operations/searcher.py +122 -0
- arionxiv/arxiv_operations/utils.py +293 -0
- arionxiv/cli/__init__.py +4 -0
- arionxiv/cli/commands/__init__.py +1 -0
- arionxiv/cli/commands/analyze.py +587 -0
- arionxiv/cli/commands/auth.py +365 -0
- arionxiv/cli/commands/chat.py +714 -0
- arionxiv/cli/commands/daily.py +482 -0
- arionxiv/cli/commands/fetch.py +217 -0
- arionxiv/cli/commands/library.py +295 -0
- arionxiv/cli/commands/preferences.py +426 -0
- arionxiv/cli/commands/search.py +254 -0
- arionxiv/cli/commands/settings_unified.py +1407 -0
- arionxiv/cli/commands/trending.py +41 -0
- arionxiv/cli/commands/welcome.py +168 -0
- arionxiv/cli/main.py +407 -0
- arionxiv/cli/ui/__init__.py +1 -0
- arionxiv/cli/ui/global_theme_manager.py +173 -0
- arionxiv/cli/ui/logo.py +127 -0
- arionxiv/cli/ui/splash.py +89 -0
- arionxiv/cli/ui/theme.py +32 -0
- arionxiv/cli/ui/theme_system.py +391 -0
- arionxiv/cli/utils/__init__.py +54 -0
- arionxiv/cli/utils/animations.py +522 -0
- arionxiv/cli/utils/api_client.py +583 -0
- arionxiv/cli/utils/api_config.py +505 -0
- arionxiv/cli/utils/command_suggestions.py +147 -0
- arionxiv/cli/utils/db_config_manager.py +254 -0
- arionxiv/github_actions_runner.py +206 -0
- arionxiv/main.py +23 -0
- arionxiv/prompts/__init__.py +9 -0
- arionxiv/prompts/prompts.py +247 -0
- arionxiv/rag_techniques/__init__.py +8 -0
- arionxiv/rag_techniques/basic_rag.py +1531 -0
- arionxiv/scheduler_daemon.py +139 -0
- arionxiv/server.py +1000 -0
- arionxiv/server_main.py +24 -0
- arionxiv/services/__init__.py +73 -0
- arionxiv/services/llm_client.py +30 -0
- arionxiv/services/llm_inference/__init__.py +58 -0
- arionxiv/services/llm_inference/groq_client.py +469 -0
- arionxiv/services/llm_inference/llm_utils.py +250 -0
- arionxiv/services/llm_inference/openrouter_client.py +564 -0
- arionxiv/services/unified_analysis_service.py +872 -0
- arionxiv/services/unified_auth_service.py +457 -0
- arionxiv/services/unified_config_service.py +456 -0
- arionxiv/services/unified_daily_dose_service.py +823 -0
- arionxiv/services/unified_database_service.py +1633 -0
- arionxiv/services/unified_llm_service.py +366 -0
- arionxiv/services/unified_paper_service.py +604 -0
- arionxiv/services/unified_pdf_service.py +522 -0
- arionxiv/services/unified_prompt_service.py +344 -0
- arionxiv/services/unified_scheduler_service.py +589 -0
- arionxiv/services/unified_user_service.py +954 -0
- arionxiv/utils/__init__.py +51 -0
- arionxiv/utils/api_helpers.py +200 -0
- arionxiv/utils/file_cleanup.py +150 -0
- arionxiv/utils/ip_helper.py +96 -0
- arionxiv-1.0.32.dist-info/METADATA +336 -0
- arionxiv-1.0.32.dist-info/RECORD +69 -0
- arionxiv-1.0.32.dist-info/WHEEL +5 -0
- arionxiv-1.0.32.dist-info/entry_points.txt +4 -0
- arionxiv-1.0.32.dist-info/licenses/LICENSE +21 -0
- 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"""
|