cite-agent 1.0.5__py3-none-any.whl → 1.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cite-agent might be problematic. Click here for more details.
- cite_agent/cli.py +374 -39
- cite_agent/cli_workflow.py +276 -0
- cite_agent/enhanced_ai_agent.py +575 -80
- cite_agent/session_manager.py +215 -0
- cite_agent/updater.py +50 -17
- cite_agent/workflow.py +427 -0
- cite_agent/workflow_integration.py +275 -0
- cite_agent-1.2.4.dist-info/METADATA +442 -0
- {cite_agent-1.0.5.dist-info → cite_agent-1.2.4.dist-info}/RECORD +13 -9
- cite_agent-1.0.5.dist-info/METADATA +0 -235
- {cite_agent-1.0.5.dist-info → cite_agent-1.2.4.dist-info}/WHEEL +0 -0
- {cite_agent-1.0.5.dist-info → cite_agent-1.2.4.dist-info}/entry_points.txt +0 -0
- {cite_agent-1.0.5.dist-info → cite_agent-1.2.4.dist-info}/licenses/LICENSE +0 -0
- {cite_agent-1.0.5.dist-info → cite_agent-1.2.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
User-Friendly Session Manager for Cite-Agent
|
|
4
|
+
Handles session detection, user choices, and authentication flow
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional, Dict, Any
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.prompt import Prompt, Confirm
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
from rich.text import Text
|
|
17
|
+
|
|
18
|
+
class SessionManager:
|
|
19
|
+
"""User-friendly session management for Cite-Agent"""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.console = Console()
|
|
23
|
+
self.session_file = Path.home() / ".nocturnal_archive" / "session.json"
|
|
24
|
+
self.config_file = Path.home() / ".nocturnal_archive" / "config.env"
|
|
25
|
+
self.session_data: Optional[Dict[str, Any]] = None
|
|
26
|
+
|
|
27
|
+
def detect_existing_session(self) -> bool:
|
|
28
|
+
"""Detect if there's an existing session and load it"""
|
|
29
|
+
if not self.session_file.exists():
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
with open(self.session_file, 'r') as f:
|
|
34
|
+
self.session_data = json.load(f)
|
|
35
|
+
return True
|
|
36
|
+
except Exception as e:
|
|
37
|
+
self.console.print(f"[red]⚠️ Session file corrupted: {e}[/red]")
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
def show_session_info(self):
|
|
41
|
+
"""Display existing session information in a user-friendly way"""
|
|
42
|
+
if not self.session_data:
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
email = self.session_data.get('email', 'Unknown')
|
|
46
|
+
user_id = self.session_data.get('user_id', 'Unknown')[:8] + "..."
|
|
47
|
+
expires_at = self.session_data.get('expires_at', 'Unknown')
|
|
48
|
+
daily_limit = self.session_data.get('daily_token_limit', 0)
|
|
49
|
+
|
|
50
|
+
# Create a nice table
|
|
51
|
+
table = Table(title="🔑 Existing Session Found", show_header=True, header_style="bold green")
|
|
52
|
+
table.add_column("Property", style="cyan", width=15)
|
|
53
|
+
table.add_column("Value", style="white")
|
|
54
|
+
|
|
55
|
+
table.add_row("Email", email)
|
|
56
|
+
table.add_row("User ID", user_id)
|
|
57
|
+
table.add_row("Daily Limit", f"{daily_limit:,} queries")
|
|
58
|
+
table.add_row("Expires", expires_at)
|
|
59
|
+
|
|
60
|
+
self.console.print()
|
|
61
|
+
self.console.print(table)
|
|
62
|
+
self.console.print()
|
|
63
|
+
|
|
64
|
+
def ask_session_choice(self) -> str:
|
|
65
|
+
"""Ask user what they want to do with the existing session"""
|
|
66
|
+
self.console.print("[bold cyan]What would you like to do?[/bold cyan]")
|
|
67
|
+
self.console.print()
|
|
68
|
+
|
|
69
|
+
# Create a nice menu
|
|
70
|
+
menu_table = Table(show_header=False, box=None, padding=(0, 1))
|
|
71
|
+
menu_table.add_column("Choice", style="bold green", width=3)
|
|
72
|
+
menu_table.add_column("Action", style="white", width=20)
|
|
73
|
+
menu_table.add_column("Description", style="dim", width=40)
|
|
74
|
+
|
|
75
|
+
menu_table.add_row("1", "Resume", "Continue with this session")
|
|
76
|
+
menu_table.add_row("2", "Switch", "Login with different account")
|
|
77
|
+
menu_table.add_row("3", "Logout", "Clear session and start fresh")
|
|
78
|
+
menu_table.add_row("4", "Help", "Show session management help")
|
|
79
|
+
|
|
80
|
+
self.console.print(menu_table)
|
|
81
|
+
self.console.print()
|
|
82
|
+
|
|
83
|
+
while True:
|
|
84
|
+
choice = Prompt.ask(
|
|
85
|
+
"Choose an option",
|
|
86
|
+
choices=["1", "2", "3", "4", "resume", "switch", "logout", "help"],
|
|
87
|
+
default="1"
|
|
88
|
+
).lower()
|
|
89
|
+
|
|
90
|
+
if choice in ["1", "resume"]:
|
|
91
|
+
return "resume"
|
|
92
|
+
elif choice in ["2", "switch"]:
|
|
93
|
+
return "switch"
|
|
94
|
+
elif choice in ["3", "logout"]:
|
|
95
|
+
return "logout"
|
|
96
|
+
elif choice in ["4", "help"]:
|
|
97
|
+
self.show_help()
|
|
98
|
+
continue
|
|
99
|
+
else:
|
|
100
|
+
self.console.print("[red]Invalid choice. Please try again.[/red]")
|
|
101
|
+
|
|
102
|
+
def show_help(self):
|
|
103
|
+
"""Show help for session management"""
|
|
104
|
+
help_text = """
|
|
105
|
+
[bold cyan]Session Management Help[/bold cyan]
|
|
106
|
+
|
|
107
|
+
[bold green]Resume:[/bold green] Continue with your existing session
|
|
108
|
+
• Use your current login and settings
|
|
109
|
+
• No need to re-authenticate
|
|
110
|
+
• All your data and preferences are preserved
|
|
111
|
+
|
|
112
|
+
[bold yellow]Switch:[/bold yellow] Login with a different account
|
|
113
|
+
• Logout from current session
|
|
114
|
+
• Start fresh with new account
|
|
115
|
+
• Previous session data will be cleared
|
|
116
|
+
|
|
117
|
+
[bold red]Logout:[/bold red] Clear session and start fresh
|
|
118
|
+
• Remove all saved login information
|
|
119
|
+
• Start completely fresh
|
|
120
|
+
• You'll need to login again
|
|
121
|
+
|
|
122
|
+
[bold blue]Session Files:[/bold blue]
|
|
123
|
+
• Session: ~/.nocturnal_archive/session.json
|
|
124
|
+
• Config: ~/.nocturnal_archive/config.env
|
|
125
|
+
|
|
126
|
+
[bold blue]Manual Session Management:[/bold blue]
|
|
127
|
+
• To clear session manually: rm ~/.nocturnal_archive/session.json
|
|
128
|
+
• To clear config: rm ~/.nocturnal_archive/config.env
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
self.console.print(Panel(help_text, title="Help", border_style="blue"))
|
|
132
|
+
self.console.print()
|
|
133
|
+
|
|
134
|
+
def clear_session(self) -> bool:
|
|
135
|
+
"""Clear the existing session"""
|
|
136
|
+
try:
|
|
137
|
+
if self.session_file.exists():
|
|
138
|
+
self.session_file.unlink()
|
|
139
|
+
if self.config_file.exists():
|
|
140
|
+
self.config_file.unlink()
|
|
141
|
+
self.console.print("[green]✅ Session cleared successfully[/green]")
|
|
142
|
+
return True
|
|
143
|
+
except Exception as e:
|
|
144
|
+
self.console.print(f"[red]❌ Error clearing session: {e}[/red]")
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
def handle_session_affirmation(self) -> str:
|
|
148
|
+
"""Main function to handle session affirmation with user-friendly interface"""
|
|
149
|
+
# Check for existing session
|
|
150
|
+
has_session = self.detect_existing_session()
|
|
151
|
+
|
|
152
|
+
if not has_session:
|
|
153
|
+
self.console.print("[yellow]No existing session found. Starting fresh...[/yellow]")
|
|
154
|
+
return "fresh"
|
|
155
|
+
|
|
156
|
+
# Show session information
|
|
157
|
+
self.show_session_info()
|
|
158
|
+
|
|
159
|
+
# Ask user what to do
|
|
160
|
+
choice = self.ask_session_choice()
|
|
161
|
+
|
|
162
|
+
if choice == "resume":
|
|
163
|
+
self.console.print("[green]✅ Resuming existing session...[/green]")
|
|
164
|
+
return "resume"
|
|
165
|
+
elif choice == "switch":
|
|
166
|
+
self.console.print("[yellow]🔄 Switching to different account...[/yellow]")
|
|
167
|
+
if self.clear_session():
|
|
168
|
+
return "fresh"
|
|
169
|
+
else:
|
|
170
|
+
return "error"
|
|
171
|
+
elif choice == "logout":
|
|
172
|
+
self.console.print("[red]🚪 Logging out...[/red]")
|
|
173
|
+
if self.clear_session():
|
|
174
|
+
return "fresh"
|
|
175
|
+
else:
|
|
176
|
+
return "error"
|
|
177
|
+
|
|
178
|
+
return "error"
|
|
179
|
+
|
|
180
|
+
def setup_environment_variables(self):
|
|
181
|
+
"""Set up environment variables for backend mode"""
|
|
182
|
+
# PRODUCTION MODE: Force backend, ensure monetization
|
|
183
|
+
# NEVER load user's .env files in production
|
|
184
|
+
|
|
185
|
+
# Set backend URL if not already set
|
|
186
|
+
if "NOCTURNAL_API_URL" not in os.environ:
|
|
187
|
+
os.environ["NOCTURNAL_API_URL"] = "https://cite-agent-api-720dfadd602c.herokuapp.com/api"
|
|
188
|
+
|
|
189
|
+
# SECURITY: Default to backend mode (USE_LOCAL_KEYS=false)
|
|
190
|
+
# This ensures users MUST authenticate and pay
|
|
191
|
+
if "USE_LOCAL_KEYS" not in os.environ:
|
|
192
|
+
os.environ["USE_LOCAL_KEYS"] = "false"
|
|
193
|
+
|
|
194
|
+
def get_session_status(self) -> Dict[str, Any]:
|
|
195
|
+
"""Get current session status for debugging"""
|
|
196
|
+
return {
|
|
197
|
+
"session_file_exists": self.session_file.exists(),
|
|
198
|
+
"config_file_exists": self.config_file.exists(),
|
|
199
|
+
"session_data": self.session_data,
|
|
200
|
+
"use_local_keys": os.environ.get("USE_LOCAL_KEYS", "not set"),
|
|
201
|
+
"api_url": os.environ.get("NOCTURNAL_API_URL", "not set")
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
def main():
|
|
205
|
+
"""Test the session manager"""
|
|
206
|
+
sm = SessionManager()
|
|
207
|
+
result = sm.handle_session_affirmation()
|
|
208
|
+
print(f"Result: {result}")
|
|
209
|
+
|
|
210
|
+
# Show status
|
|
211
|
+
status = sm.get_session_status()
|
|
212
|
+
print(f"Status: {status}")
|
|
213
|
+
|
|
214
|
+
if __name__ == "__main__":
|
|
215
|
+
main()
|
cite_agent/updater.py
CHANGED
|
@@ -12,18 +12,29 @@ from pathlib import Path
|
|
|
12
12
|
from typing import Optional, Dict, Any
|
|
13
13
|
|
|
14
14
|
try:
|
|
15
|
-
|
|
15
|
+
# Use modern importlib.metadata instead of deprecated pkg_resources
|
|
16
|
+
from importlib.metadata import version as get_version
|
|
17
|
+
pkg_resources = None # Not needed anymore
|
|
16
18
|
except ImportError:
|
|
17
|
-
|
|
19
|
+
# Fallback for Python < 3.8
|
|
20
|
+
try:
|
|
21
|
+
import warnings
|
|
22
|
+
with warnings.catch_warnings():
|
|
23
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
24
|
+
warnings.filterwarnings("ignore", category=UserWarning)
|
|
25
|
+
import pkg_resources
|
|
26
|
+
except ImportError:
|
|
27
|
+
pkg_resources = None
|
|
28
|
+
get_version = None
|
|
18
29
|
|
|
19
30
|
class NocturnalUpdater:
|
|
20
31
|
"""Handles automatic updates for Nocturnal Archive"""
|
|
21
32
|
|
|
22
33
|
def __init__(self):
|
|
23
34
|
self.current_version = self.get_current_version()
|
|
24
|
-
self.package_name = "nocturnal-archive"
|
|
35
|
+
self.package_name = "cite-agent" # Fixed: was "nocturnal-archive"
|
|
25
36
|
self.pypi_url = f"https://pypi.org/pypi/{self.package_name}/json"
|
|
26
|
-
self.kill_switch_url = "https://api.
|
|
37
|
+
self.kill_switch_url = "https://cite-agent-api-720dfadd602c.herokuapp.com/api/health"
|
|
27
38
|
|
|
28
39
|
def check_kill_switch(self) -> Dict[str, Any]:
|
|
29
40
|
"""Check if kill switch is activated"""
|
|
@@ -37,16 +48,23 @@ class NocturnalUpdater:
|
|
|
37
48
|
|
|
38
49
|
def get_current_version(self) -> str:
|
|
39
50
|
"""Get current installed version"""
|
|
51
|
+
# Try modern importlib.metadata first
|
|
52
|
+
try:
|
|
53
|
+
return get_version(self.package_name)
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
# Fallback to pkg_resources (deprecated)
|
|
40
58
|
if pkg_resources:
|
|
41
59
|
try:
|
|
42
60
|
return pkg_resources.get_distribution(self.package_name).version
|
|
43
|
-
except
|
|
61
|
+
except Exception:
|
|
44
62
|
pass
|
|
45
63
|
|
|
46
|
-
#
|
|
64
|
+
# Last resort: try to get version from installed package
|
|
47
65
|
try:
|
|
48
|
-
import
|
|
49
|
-
return getattr(
|
|
66
|
+
import cite_agent
|
|
67
|
+
return getattr(cite_agent, '__version__', '1.0.0')
|
|
50
68
|
except ImportError:
|
|
51
69
|
return "1.0.0"
|
|
52
70
|
|
|
@@ -103,32 +121,47 @@ class NocturnalUpdater:
|
|
|
103
121
|
except:
|
|
104
122
|
return False
|
|
105
123
|
|
|
106
|
-
def update_package(self, force: bool = False) -> bool:
|
|
124
|
+
def update_package(self, force: bool = False, silent: bool = False) -> bool:
|
|
107
125
|
"""Update the package to latest version"""
|
|
108
126
|
try:
|
|
109
|
-
|
|
127
|
+
if not silent:
|
|
128
|
+
print("🔄 Updating cite-agent...")
|
|
110
129
|
|
|
111
130
|
# Check if update is needed
|
|
112
131
|
if not force:
|
|
113
132
|
update_info = self.check_for_updates()
|
|
114
133
|
if not update_info or not update_info["available"]:
|
|
115
|
-
|
|
134
|
+
if not silent:
|
|
135
|
+
print("✅ No updates available")
|
|
116
136
|
return True
|
|
117
137
|
|
|
118
|
-
# Perform update
|
|
119
|
-
cmd = [sys.executable, "-m", "pip", "install", "--upgrade", self.package_name]
|
|
138
|
+
# Perform update with user flag to avoid system package conflicts
|
|
139
|
+
cmd = [sys.executable, "-m", "pip", "install", "--upgrade", "--user", self.package_name]
|
|
120
140
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
121
141
|
|
|
122
142
|
if result.returncode == 0:
|
|
123
|
-
|
|
124
|
-
|
|
143
|
+
# Create flag file to notify next launch
|
|
144
|
+
try:
|
|
145
|
+
from pathlib import Path
|
|
146
|
+
update_flag = Path.home() / ".nocturnal_archive" / ".updated"
|
|
147
|
+
update_flag.parent.mkdir(exist_ok=True)
|
|
148
|
+
update_flag.write_text(self.get_current_version())
|
|
149
|
+
except:
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
if not silent:
|
|
153
|
+
new_version = self.get_current_version()
|
|
154
|
+
print(f"✅ Updated to version {new_version}")
|
|
155
|
+
print("🔄 Restart cite-agent to use the new version")
|
|
125
156
|
return True
|
|
126
157
|
else:
|
|
127
|
-
|
|
158
|
+
if not silent:
|
|
159
|
+
print(f"❌ Update failed: {result.stderr}")
|
|
128
160
|
return False
|
|
129
161
|
|
|
130
162
|
except Exception as e:
|
|
131
|
-
|
|
163
|
+
if not silent:
|
|
164
|
+
print(f"❌ Update error: {e}")
|
|
132
165
|
return False
|
|
133
166
|
|
|
134
167
|
def show_update_status(self):
|