cite-agent 1.0.4__py3-none-any.whl → 1.2.3__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/__init__.py +1 -1
- cite_agent/account_client.py +19 -46
- cite_agent/agent_backend_only.py +30 -4
- cite_agent/cli.py +397 -64
- cite_agent/cli_conversational.py +294 -0
- cite_agent/cli_workflow.py +276 -0
- cite_agent/enhanced_ai_agent.py +3222 -117
- cite_agent/session_manager.py +215 -0
- cite_agent/setup_config.py +5 -21
- cite_agent/streaming_ui.py +252 -0
- cite_agent/updater.py +50 -17
- cite_agent/workflow.py +427 -0
- cite_agent/workflow_integration.py +275 -0
- cite_agent-1.2.3.dist-info/METADATA +442 -0
- cite_agent-1.2.3.dist-info/RECORD +54 -0
- {cite_agent-1.0.4.dist-info → cite_agent-1.2.3.dist-info}/top_level.txt +1 -0
- src/__init__.py +1 -0
- src/services/__init__.py +132 -0
- src/services/auth_service/__init__.py +3 -0
- src/services/auth_service/auth_manager.py +33 -0
- src/services/graph/__init__.py +1 -0
- src/services/graph/knowledge_graph.py +194 -0
- src/services/llm_service/__init__.py +5 -0
- src/services/llm_service/llm_manager.py +495 -0
- src/services/paper_service/__init__.py +5 -0
- src/services/paper_service/openalex.py +231 -0
- src/services/performance_service/__init__.py +1 -0
- src/services/performance_service/rust_performance.py +395 -0
- src/services/research_service/__init__.py +23 -0
- src/services/research_service/chatbot.py +2056 -0
- src/services/research_service/citation_manager.py +436 -0
- src/services/research_service/context_manager.py +1441 -0
- src/services/research_service/conversation_manager.py +597 -0
- src/services/research_service/critical_paper_detector.py +577 -0
- src/services/research_service/enhanced_research.py +121 -0
- src/services/research_service/enhanced_synthesizer.py +375 -0
- src/services/research_service/query_generator.py +777 -0
- src/services/research_service/synthesizer.py +1273 -0
- src/services/search_service/__init__.py +5 -0
- src/services/search_service/indexer.py +186 -0
- src/services/search_service/search_engine.py +342 -0
- src/services/simple_enhanced_main.py +287 -0
- cite_agent/__distribution__.py +0 -7
- cite_agent-1.0.4.dist-info/METADATA +0 -234
- cite_agent-1.0.4.dist-info/RECORD +0 -23
- {cite_agent-1.0.4.dist-info → cite_agent-1.2.3.dist-info}/WHEEL +0 -0
- {cite_agent-1.0.4.dist-info → cite_agent-1.2.3.dist-info}/entry_points.txt +0 -0
- {cite_agent-1.0.4.dist-info → cite_agent-1.2.3.dist-info}/licenses/LICENSE +0 -0
cite_agent/cli.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
|
|
3
|
+
Cite Agent CLI - Command Line Interface
|
|
4
4
|
Provides a terminal interface similar to cursor-agent
|
|
5
5
|
"""
|
|
6
6
|
|
|
@@ -24,14 +24,19 @@ from .enhanced_ai_agent import EnhancedNocturnalAgent, ChatRequest
|
|
|
24
24
|
from .setup_config import NocturnalConfig, DEFAULT_QUERY_LIMIT, MANAGED_SECRETS
|
|
25
25
|
from .telemetry import TelemetryManager
|
|
26
26
|
from .updater import NocturnalUpdater
|
|
27
|
+
from .cli_workflow import WorkflowCLI
|
|
28
|
+
from .workflow import WorkflowManager, Paper, parse_paper_from_response
|
|
29
|
+
from .session_manager import SessionManager
|
|
27
30
|
|
|
28
31
|
class NocturnalCLI:
|
|
29
|
-
"""Command Line Interface for
|
|
32
|
+
"""Command Line Interface for Cite Agent"""
|
|
30
33
|
|
|
31
34
|
def __init__(self):
|
|
32
35
|
self.agent: Optional[EnhancedNocturnalAgent] = None
|
|
33
36
|
self.session_id = f"cli_{os.getpid()}"
|
|
34
37
|
self.telemetry = None
|
|
38
|
+
self.workflow = WorkflowManager()
|
|
39
|
+
self.workflow_cli = WorkflowCLI()
|
|
35
40
|
self.console = Console(theme=Theme({
|
|
36
41
|
"banner": "bold magenta",
|
|
37
42
|
"success": "bold green",
|
|
@@ -49,10 +54,32 @@ class NocturnalCLI:
|
|
|
49
54
|
"If you see an auto-update notice, the CLI will restart itself to load the latest build.",
|
|
50
55
|
]
|
|
51
56
|
|
|
52
|
-
|
|
57
|
+
def handle_user_friendly_session(self):
|
|
58
|
+
"""Handle session management with user-friendly interface"""
|
|
59
|
+
session_manager = SessionManager()
|
|
60
|
+
|
|
61
|
+
# Set up environment variables for backend mode
|
|
62
|
+
session_manager.setup_environment_variables()
|
|
63
|
+
|
|
64
|
+
# Handle session affirmation
|
|
65
|
+
result = session_manager.handle_session_affirmation()
|
|
66
|
+
|
|
67
|
+
if result == "error":
|
|
68
|
+
self.console.print("[red]❌ Session management failed. Please try again.[/red]")
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
async def initialize(self, non_interactive: bool = False):
|
|
53
74
|
"""Initialize the agent with automatic updates"""
|
|
54
75
|
# Check for update notifications from previous runs
|
|
55
76
|
self._check_update_notification()
|
|
77
|
+
|
|
78
|
+
# Handle user-friendly session management (skip prompts in non-interactive mode)
|
|
79
|
+
if not non_interactive:
|
|
80
|
+
if not self.handle_user_friendly_session():
|
|
81
|
+
return False
|
|
82
|
+
|
|
56
83
|
self._show_intro_panel()
|
|
57
84
|
|
|
58
85
|
self._enforce_latest_build()
|
|
@@ -63,15 +90,30 @@ class NocturnalCLI:
|
|
|
63
90
|
self.telemetry = TelemetryManager.get()
|
|
64
91
|
|
|
65
92
|
if not config.check_setup():
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
93
|
+
# Check if we have env vars or session file (non-interactive mode)
|
|
94
|
+
import os
|
|
95
|
+
from pathlib import Path
|
|
96
|
+
session_file = Path.home() / ".nocturnal_archive" / "session.json"
|
|
97
|
+
has_env_creds = os.getenv("NOCTURNAL_ACCOUNT_EMAIL") and os.getenv("NOCTURNAL_ACCOUNT_PASSWORD")
|
|
98
|
+
|
|
99
|
+
if session_file.exists() or has_env_creds:
|
|
100
|
+
# Skip interactive setup if session exists or env vars present
|
|
101
|
+
self.console.print("[success]⚙️ Using saved credentials.[/success]")
|
|
102
|
+
else:
|
|
103
|
+
# Need interactive setup
|
|
104
|
+
if non_interactive:
|
|
105
|
+
self.console.print("[error]❌ Not authenticated. Run 'cite-agent --setup' to configure.[/error]")
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
self.console.print("\n[warning]👋 Hey there, looks like this machine hasn't met Nocturnal yet.[/warning]")
|
|
109
|
+
self.console.print("[banner]Let's get you signed in — this only takes a minute.[/banner]")
|
|
110
|
+
try:
|
|
111
|
+
if not config.interactive_setup():
|
|
112
|
+
self.console.print("[error]❌ Setup was cancelled. Exiting without starting the agent.[/error]")
|
|
113
|
+
return False
|
|
114
|
+
except (KeyboardInterrupt, EOFError):
|
|
115
|
+
self.console.print("\n[error]❌ Setup interrupted. Exiting without starting the agent.[/error]")
|
|
71
116
|
return False
|
|
72
|
-
except (KeyboardInterrupt, EOFError):
|
|
73
|
-
self.console.print("\n[error]❌ Setup interrupted. Exiting without starting the agent.[/error]")
|
|
74
|
-
return False
|
|
75
117
|
config.setup_environment()
|
|
76
118
|
TelemetryManager.refresh()
|
|
77
119
|
self.telemetry = TelemetryManager.get()
|
|
@@ -90,8 +132,10 @@ class NocturnalCLI:
|
|
|
90
132
|
self.console.print(" • Check your internet connection to the backend")
|
|
91
133
|
return False
|
|
92
134
|
|
|
93
|
-
|
|
94
|
-
|
|
135
|
+
# Only show panels in debug mode or interactive mode
|
|
136
|
+
if not non_interactive or os.getenv("NOCTURNAL_DEBUG", "").lower() == "1":
|
|
137
|
+
self._show_ready_panel()
|
|
138
|
+
# Beta banner removed for production
|
|
95
139
|
return True
|
|
96
140
|
|
|
97
141
|
def _show_beta_banner(self):
|
|
@@ -120,13 +164,18 @@ class NocturnalCLI:
|
|
|
120
164
|
self.console.print(panel)
|
|
121
165
|
|
|
122
166
|
def _show_intro_panel(self):
|
|
167
|
+
# Only show in debug mode or interactive mode
|
|
168
|
+
debug_mode = os.getenv("NOCTURNAL_DEBUG", "").lower() == "1"
|
|
169
|
+
if not debug_mode:
|
|
170
|
+
return
|
|
171
|
+
|
|
123
172
|
message = (
|
|
124
173
|
"Warming up your research cockpit…\n"
|
|
125
174
|
"[dim]Loading config, telemetry, and background update checks.[/dim]"
|
|
126
175
|
)
|
|
127
176
|
panel = Panel(
|
|
128
177
|
message,
|
|
129
|
-
title="
|
|
178
|
+
title="🤖 Initializing Cite Agent",
|
|
130
179
|
border_style="magenta",
|
|
131
180
|
padding=(1, 2),
|
|
132
181
|
box=box.ROUNDED,
|
|
@@ -137,7 +186,7 @@ class NocturnalCLI:
|
|
|
137
186
|
panel = Panel(
|
|
138
187
|
"Systems check complete.\n"
|
|
139
188
|
"Type [bold]help[/] for commands or [bold]tips[/] for power moves.",
|
|
140
|
-
title="✅
|
|
189
|
+
title="✅ Cite Agent ready!",
|
|
141
190
|
border_style="green",
|
|
142
191
|
padding=(1, 2),
|
|
143
192
|
box=box.ROUNDED,
|
|
@@ -146,22 +195,9 @@ class NocturnalCLI:
|
|
|
146
195
|
|
|
147
196
|
def _enforce_latest_build(self):
|
|
148
197
|
"""Ensure the CLI is running the most recent published build."""
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
except Exception:
|
|
153
|
-
return
|
|
154
|
-
|
|
155
|
-
if not update_info or not update_info.get("available"):
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
latest_version = update_info.get("latest", "latest")
|
|
159
|
-
self.console.print(f"[banner]⬆️ Updating Nocturnal Archive to {latest_version} before launch...[/banner]")
|
|
160
|
-
|
|
161
|
-
if updater.update_package():
|
|
162
|
-
self._save_update_notification(latest_version)
|
|
163
|
-
self.console.print("[warning]♻️ Restarting to finish applying the update...[/warning]")
|
|
164
|
-
self._restart_cli()
|
|
198
|
+
# Skip update check for beta - not published to PyPI yet
|
|
199
|
+
# TODO: Re-enable after PyPI publication
|
|
200
|
+
return
|
|
165
201
|
|
|
166
202
|
def _restart_cli(self):
|
|
167
203
|
"""Re-exec the CLI using the current interpreter and arguments."""
|
|
@@ -232,27 +268,56 @@ class NocturnalCLI:
|
|
|
232
268
|
if user_input.lower() == 'feedback':
|
|
233
269
|
self.collect_feedback()
|
|
234
270
|
continue
|
|
235
|
-
|
|
271
|
+
|
|
272
|
+
# Handle workflow commands
|
|
273
|
+
if user_input.lower() in ['show my library', 'library', 'list library']:
|
|
274
|
+
self.list_library()
|
|
275
|
+
continue
|
|
276
|
+
if user_input.lower() in ['show history', 'history']:
|
|
277
|
+
self.show_history()
|
|
278
|
+
continue
|
|
279
|
+
if user_input.lower().startswith('export bibtex'):
|
|
280
|
+
self.export_library_bibtex()
|
|
281
|
+
continue
|
|
282
|
+
if user_input.lower().startswith('export markdown'):
|
|
283
|
+
self.export_library_markdown()
|
|
284
|
+
continue
|
|
285
|
+
|
|
236
286
|
if not user_input:
|
|
237
287
|
continue
|
|
238
288
|
except (EOFError, KeyboardInterrupt):
|
|
239
289
|
self.console.print("\n[warning]👋 Goodbye![/warning]")
|
|
240
290
|
break
|
|
241
291
|
|
|
242
|
-
self.console.print("[bold violet]🤖 Agent[/]: ", end="", highlight=False)
|
|
243
|
-
|
|
244
292
|
try:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
user_id="cli_user",
|
|
248
|
-
conversation_id=self.session_id
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
response = await self.agent.process_request(request)
|
|
293
|
+
from rich.spinner import Spinner
|
|
294
|
+
from rich.live import Live
|
|
252
295
|
|
|
296
|
+
# Show loading indicator while processing
|
|
297
|
+
with Live(Spinner("dots", text="[dim]Thinking...[/dim]"), console=self.console, transient=True):
|
|
298
|
+
request = ChatRequest(
|
|
299
|
+
question=user_input,
|
|
300
|
+
user_id="cli_user",
|
|
301
|
+
conversation_id=self.session_id
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
response = await self.agent.process_request(request)
|
|
305
|
+
|
|
253
306
|
# Print response with proper formatting
|
|
307
|
+
self.console.print("[bold violet]🤖 Agent[/]: ", end="", highlight=False)
|
|
254
308
|
self.console.print(response.response)
|
|
255
|
-
|
|
309
|
+
|
|
310
|
+
# Save to history automatically
|
|
311
|
+
self.workflow.save_query_result(
|
|
312
|
+
query=user_input,
|
|
313
|
+
response=response.response,
|
|
314
|
+
metadata={
|
|
315
|
+
"tools_used": response.tools_used,
|
|
316
|
+
"tokens_used": response.tokens_used,
|
|
317
|
+
"confidence_score": response.confidence_score
|
|
318
|
+
}
|
|
319
|
+
)
|
|
320
|
+
|
|
256
321
|
# Show usage stats occasionally
|
|
257
322
|
if hasattr(self.agent, 'daily_token_usage') and self.agent.daily_token_usage > 0:
|
|
258
323
|
stats = self.agent.get_usage_stats()
|
|
@@ -268,25 +333,26 @@ class NocturnalCLI:
|
|
|
268
333
|
|
|
269
334
|
async def single_query(self, question: str):
|
|
270
335
|
"""Process a single query"""
|
|
271
|
-
if not await self.initialize():
|
|
336
|
+
if not await self.initialize(non_interactive=True):
|
|
272
337
|
return
|
|
273
338
|
|
|
274
339
|
try:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
request = ChatRequest(
|
|
279
|
-
question=question,
|
|
280
|
-
user_id="cli_user",
|
|
281
|
-
conversation_id=self.session_id
|
|
282
|
-
)
|
|
340
|
+
from rich.spinner import Spinner
|
|
341
|
+
from rich.live import Live
|
|
283
342
|
|
|
284
|
-
|
|
343
|
+
# Show clean loading indicator
|
|
344
|
+
with Live(Spinner("dots", text=f"[cyan]{question}[/cyan]"), console=self.console, transient=True):
|
|
345
|
+
request = ChatRequest(
|
|
346
|
+
question=question,
|
|
347
|
+
user_id="cli_user",
|
|
348
|
+
conversation_id=self.session_id
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
response = await self.agent.process_request(request)
|
|
285
352
|
|
|
286
353
|
self.console.print(f"\n📝 [bold]Response[/]:\n{response.response}")
|
|
287
354
|
|
|
288
|
-
|
|
289
|
-
self.console.print(f"\n🔧 Tools used: {', '.join(response.tools_used)}")
|
|
355
|
+
# Tools used removed for cleaner output
|
|
290
356
|
|
|
291
357
|
if response.tokens_used > 0:
|
|
292
358
|
stats = self.agent.get_usage_stats()
|
|
@@ -319,7 +385,7 @@ class NocturnalCLI:
|
|
|
319
385
|
"""Collect feedback from the user and store it locally"""
|
|
320
386
|
self.console.print(
|
|
321
387
|
Panel(
|
|
322
|
-
"Share what
|
|
388
|
+
"Share what's working, what feels rough, or any paper/finance workflows you wish existed.\n"
|
|
323
389
|
"Press Enter on an empty line to finish.",
|
|
324
390
|
title="📝 Beta Feedback",
|
|
325
391
|
border_style="cyan",
|
|
@@ -350,7 +416,7 @@ class NocturnalCLI:
|
|
|
350
416
|
|
|
351
417
|
content = "\n".join(lines)
|
|
352
418
|
with open(feedback_path, "w", encoding="utf-8") as handle:
|
|
353
|
-
handle.write("#
|
|
419
|
+
handle.write("# Cite Agent Feedback\n")
|
|
354
420
|
handle.write(f"timestamp = {timestamp}Z\n")
|
|
355
421
|
handle.write("\n")
|
|
356
422
|
handle.write(content)
|
|
@@ -362,10 +428,184 @@ class NocturnalCLI:
|
|
|
362
428
|
self.console.print("[dim]Attach that file when you send feedback to the team.[/dim]")
|
|
363
429
|
return 0
|
|
364
430
|
|
|
431
|
+
def list_library(self, tag: Optional[str] = None):
|
|
432
|
+
"""List papers in local library"""
|
|
433
|
+
papers = self.workflow.list_papers(tag=tag)
|
|
434
|
+
|
|
435
|
+
if not papers:
|
|
436
|
+
self.console.print("[warning]No papers in library yet.[/warning]")
|
|
437
|
+
self.console.print("[dim]Use --save-paper after a search to add papers.[/dim]")
|
|
438
|
+
return
|
|
439
|
+
|
|
440
|
+
table = Table(title=f"📚 Library ({len(papers)} papers)", box=box.ROUNDED)
|
|
441
|
+
table.add_column("ID", style="cyan")
|
|
442
|
+
table.add_column("Title", style="bold")
|
|
443
|
+
table.add_column("Authors", style="dim")
|
|
444
|
+
table.add_column("Year", justify="right")
|
|
445
|
+
table.add_column("Tags", style="yellow")
|
|
446
|
+
|
|
447
|
+
for paper in papers[:20]: # Show first 20
|
|
448
|
+
authors_str = paper.authors[0] if paper.authors else "Unknown"
|
|
449
|
+
if len(paper.authors) > 1:
|
|
450
|
+
authors_str += " et al."
|
|
451
|
+
|
|
452
|
+
tags_str = ", ".join(paper.tags) if paper.tags else ""
|
|
453
|
+
|
|
454
|
+
table.add_row(
|
|
455
|
+
paper.paper_id[:8],
|
|
456
|
+
paper.title[:50] + "..." if len(paper.title) > 50 else paper.title,
|
|
457
|
+
authors_str,
|
|
458
|
+
str(paper.year),
|
|
459
|
+
tags_str
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
self.console.print(table)
|
|
463
|
+
|
|
464
|
+
if len(papers) > 20:
|
|
465
|
+
self.console.print(f"[dim]... and {len(papers) - 20} more papers[/dim]")
|
|
466
|
+
|
|
467
|
+
def export_library_bibtex(self):
|
|
468
|
+
"""Export library to BibTeX"""
|
|
469
|
+
success = self.workflow.export_to_bibtex()
|
|
470
|
+
if success:
|
|
471
|
+
self.console.print(f"[success]✅ Exported to:[/success] [bold]{self.workflow.bibtex_file}[/bold]")
|
|
472
|
+
self.console.print("[dim]Import this file into Zotero, Mendeley, or any citation manager.[/dim]")
|
|
473
|
+
else:
|
|
474
|
+
self.console.print("[error]❌ Failed to export BibTeX[/error]")
|
|
475
|
+
|
|
476
|
+
def export_library_markdown(self):
|
|
477
|
+
"""Export library to Markdown"""
|
|
478
|
+
success = self.workflow.export_to_markdown()
|
|
479
|
+
if success:
|
|
480
|
+
export_file = self.workflow.exports_dir / f"papers_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md"
|
|
481
|
+
self.console.print(f"[success]✅ Exported to:[/success] [bold]{export_file}[/bold]")
|
|
482
|
+
self.console.print("[dim]Open in Obsidian, Notion, or any markdown editor.[/dim]")
|
|
483
|
+
else:
|
|
484
|
+
self.console.print("[error]❌ Failed to export Markdown[/error]")
|
|
485
|
+
|
|
486
|
+
def show_history(self, limit: int = 10):
|
|
487
|
+
"""Show recent query history"""
|
|
488
|
+
history = self.workflow.get_history()[:limit]
|
|
489
|
+
|
|
490
|
+
if not history:
|
|
491
|
+
self.console.print("[warning]No query history yet.[/warning]")
|
|
492
|
+
return
|
|
493
|
+
|
|
494
|
+
table = Table(title=f"📜 Recent Queries", box=box.ROUNDED)
|
|
495
|
+
table.add_column("Time", style="cyan")
|
|
496
|
+
table.add_column("Query", style="bold")
|
|
497
|
+
table.add_column("Tools", style="dim")
|
|
498
|
+
|
|
499
|
+
for entry in history:
|
|
500
|
+
timestamp = datetime.fromisoformat(entry['timestamp'])
|
|
501
|
+
time_str = timestamp.strftime("%m/%d %H:%M")
|
|
502
|
+
query_str = entry['query'][:60] + "..." if len(entry['query']) > 60 else entry['query']
|
|
503
|
+
tools_str = ", ".join(entry.get('metadata', {}).get('tools_used', []))
|
|
504
|
+
|
|
505
|
+
table.add_row(time_str, query_str, tools_str)
|
|
506
|
+
|
|
507
|
+
self.console.print(table)
|
|
508
|
+
|
|
509
|
+
def search_library_interactive(self, query: str):
|
|
510
|
+
"""Search papers in library"""
|
|
511
|
+
results = self.workflow.search_library(query)
|
|
512
|
+
|
|
513
|
+
if not results:
|
|
514
|
+
self.console.print(f"[warning]No papers found matching '{query}'[/warning]")
|
|
515
|
+
return
|
|
516
|
+
|
|
517
|
+
self.console.print(f"[success]Found {len(results)} paper(s)[/success]\n")
|
|
518
|
+
|
|
519
|
+
for i, paper in enumerate(results, 1):
|
|
520
|
+
self.console.print(f"[bold cyan]{i}. {paper.title}[/bold cyan]")
|
|
521
|
+
authors_str = ", ".join(paper.authors) if paper.authors else "Unknown"
|
|
522
|
+
self.console.print(f" Authors: {authors_str}")
|
|
523
|
+
self.console.print(f" Year: {paper.year} | ID: {paper.paper_id[:8]}")
|
|
524
|
+
if paper.tags:
|
|
525
|
+
self.console.print(f" Tags: {', '.join(paper.tags)}")
|
|
526
|
+
self.console.print()
|
|
527
|
+
|
|
528
|
+
async def single_query_with_workflow(self, question: str, save_to_library: bool = False,
|
|
529
|
+
copy_to_clipboard: bool = False, export_format: Optional[str] = None):
|
|
530
|
+
"""Process a single query with workflow integration"""
|
|
531
|
+
if not await self.initialize(non_interactive=True):
|
|
532
|
+
return
|
|
533
|
+
|
|
534
|
+
try:
|
|
535
|
+
self.console.print(f"🤖 [bold]Processing[/]: {question}")
|
|
536
|
+
self.console.rule(style="magenta")
|
|
537
|
+
|
|
538
|
+
request = ChatRequest(
|
|
539
|
+
question=question,
|
|
540
|
+
user_id="cli_user",
|
|
541
|
+
conversation_id=self.session_id
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
response = await self.agent.process_request(request)
|
|
545
|
+
|
|
546
|
+
self.console.print(f"\n📝 [bold]Response[/]:\n{response.response}")
|
|
547
|
+
|
|
548
|
+
# Tools used removed for cleaner output
|
|
549
|
+
|
|
550
|
+
if response.tokens_used > 0:
|
|
551
|
+
stats = self.agent.get_usage_stats()
|
|
552
|
+
self.console.print(
|
|
553
|
+
f"\n📊 Tokens used: {response.tokens_used} "
|
|
554
|
+
f"(Daily usage: {stats['usage_percentage']:.1f}%)"
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
# Workflow integrations
|
|
558
|
+
if copy_to_clipboard:
|
|
559
|
+
if self.workflow.copy_to_clipboard(response.response):
|
|
560
|
+
self.console.print("[success]📋 Copied to clipboard[/success]")
|
|
561
|
+
|
|
562
|
+
if export_format:
|
|
563
|
+
if export_format == "bibtex":
|
|
564
|
+
# Try to parse paper from response
|
|
565
|
+
paper = parse_paper_from_response(response.response)
|
|
566
|
+
if paper:
|
|
567
|
+
bibtex = paper.to_bibtex()
|
|
568
|
+
self.console.print(f"\n[bold]BibTeX:[/bold]\n{bibtex}")
|
|
569
|
+
if copy_to_clipboard:
|
|
570
|
+
self.workflow.copy_to_clipboard(bibtex)
|
|
571
|
+
else:
|
|
572
|
+
self.console.print("[warning]Could not extract paper info for BibTeX[/warning]")
|
|
573
|
+
|
|
574
|
+
elif export_format == "apa":
|
|
575
|
+
paper = parse_paper_from_response(response.response)
|
|
576
|
+
if paper:
|
|
577
|
+
apa = paper.to_apa_citation()
|
|
578
|
+
self.console.print(f"\n[bold]APA Citation:[/bold]\n{apa}")
|
|
579
|
+
if copy_to_clipboard:
|
|
580
|
+
self.workflow.copy_to_clipboard(apa)
|
|
581
|
+
|
|
582
|
+
# Save to history
|
|
583
|
+
self.workflow.save_query_result(
|
|
584
|
+
query=question,
|
|
585
|
+
response=response.response,
|
|
586
|
+
metadata={
|
|
587
|
+
"tools_used": response.tools_used,
|
|
588
|
+
"tokens_used": response.tokens_used,
|
|
589
|
+
"confidence_score": response.confidence_score
|
|
590
|
+
}
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
if save_to_library:
|
|
594
|
+
paper = parse_paper_from_response(response.response)
|
|
595
|
+
if paper:
|
|
596
|
+
if self.workflow.add_paper(paper):
|
|
597
|
+
self.console.print(f"[success]✅ Saved to library (ID: {paper.paper_id[:8]})[/success]")
|
|
598
|
+
else:
|
|
599
|
+
self.console.print("[error]❌ Failed to save to library[/error]")
|
|
600
|
+
|
|
601
|
+
finally:
|
|
602
|
+
if self.agent:
|
|
603
|
+
await self.agent.close()
|
|
604
|
+
|
|
365
605
|
def main():
|
|
366
606
|
"""Main CLI entry point"""
|
|
367
607
|
parser = argparse.ArgumentParser(
|
|
368
|
-
description="
|
|
608
|
+
description="Cite Agent - AI Research Assistant with real data",
|
|
369
609
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
370
610
|
epilog="""
|
|
371
611
|
Examples:
|
|
@@ -421,10 +661,16 @@ Examples:
|
|
|
421
661
|
)
|
|
422
662
|
|
|
423
663
|
parser.add_argument(
|
|
424
|
-
'--feedback',
|
|
425
|
-
action='store_true',
|
|
664
|
+
'--feedback',
|
|
665
|
+
action='store_true',
|
|
426
666
|
help='Capture beta feedback and save it locally'
|
|
427
667
|
)
|
|
668
|
+
|
|
669
|
+
parser.add_argument(
|
|
670
|
+
'--workflow',
|
|
671
|
+
action='store_true',
|
|
672
|
+
help='Start workflow mode for integrated research management'
|
|
673
|
+
)
|
|
428
674
|
|
|
429
675
|
parser.add_argument(
|
|
430
676
|
'--import-secrets',
|
|
@@ -438,12 +684,67 @@ Examples:
|
|
|
438
684
|
help='Fail secret import if keyring is unavailable'
|
|
439
685
|
)
|
|
440
686
|
|
|
687
|
+
# Workflow integration arguments
|
|
688
|
+
parser.add_argument(
|
|
689
|
+
'--library',
|
|
690
|
+
action='store_true',
|
|
691
|
+
help='List all papers in local library'
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
parser.add_argument(
|
|
695
|
+
'--export-bibtex',
|
|
696
|
+
action='store_true',
|
|
697
|
+
help='Export library to BibTeX format'
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
parser.add_argument(
|
|
701
|
+
'--export-markdown',
|
|
702
|
+
action='store_true',
|
|
703
|
+
help='Export library to Markdown format'
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
parser.add_argument(
|
|
707
|
+
'--history',
|
|
708
|
+
action='store_true',
|
|
709
|
+
help='Show recent query history'
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
parser.add_argument(
|
|
713
|
+
'--search-library',
|
|
714
|
+
metavar='QUERY',
|
|
715
|
+
help='Search papers in local library'
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
parser.add_argument(
|
|
719
|
+
'--save',
|
|
720
|
+
action='store_true',
|
|
721
|
+
help='Save query results to library (use with query)'
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
parser.add_argument(
|
|
725
|
+
'--copy',
|
|
726
|
+
action='store_true',
|
|
727
|
+
help='Copy results to clipboard (use with query)'
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
parser.add_argument(
|
|
731
|
+
'--format',
|
|
732
|
+
choices=['bibtex', 'apa', 'markdown'],
|
|
733
|
+
help='Export format for citations (use with query)'
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
parser.add_argument(
|
|
737
|
+
'--tag',
|
|
738
|
+
metavar='TAG',
|
|
739
|
+
help='Filter library by tag'
|
|
740
|
+
)
|
|
741
|
+
|
|
441
742
|
args = parser.parse_args()
|
|
442
743
|
|
|
443
744
|
# Handle version
|
|
444
745
|
if args.version:
|
|
445
|
-
print("Cite
|
|
446
|
-
print("AI Research Assistant
|
|
746
|
+
print("Cite Agent v1.2.2")
|
|
747
|
+
print("AI Research Assistant with real data integration")
|
|
447
748
|
return
|
|
448
749
|
|
|
449
750
|
if args.tips or (args.query and args.query.lower() == "tips" and not args.interactive):
|
|
@@ -456,6 +757,29 @@ Examples:
|
|
|
456
757
|
exit_code = cli.collect_feedback()
|
|
457
758
|
sys.exit(exit_code)
|
|
458
759
|
|
|
760
|
+
# Handle workflow commands (no agent initialization needed)
|
|
761
|
+
cli = NocturnalCLI()
|
|
762
|
+
|
|
763
|
+
if args.library:
|
|
764
|
+
cli.list_library(tag=args.tag)
|
|
765
|
+
sys.exit(0)
|
|
766
|
+
|
|
767
|
+
if args.export_bibtex:
|
|
768
|
+
cli.export_library_bibtex()
|
|
769
|
+
sys.exit(0)
|
|
770
|
+
|
|
771
|
+
if args.export_markdown:
|
|
772
|
+
cli.export_library_markdown()
|
|
773
|
+
sys.exit(0)
|
|
774
|
+
|
|
775
|
+
if args.history:
|
|
776
|
+
cli.show_history(limit=20)
|
|
777
|
+
sys.exit(0)
|
|
778
|
+
|
|
779
|
+
if args.search_library:
|
|
780
|
+
cli.search_library_interactive(args.search_library)
|
|
781
|
+
sys.exit(0)
|
|
782
|
+
|
|
459
783
|
# Handle secret import before setup as it can be used non-interactively
|
|
460
784
|
if args.import_secrets:
|
|
461
785
|
config = NocturnalConfig()
|
|
@@ -494,12 +818,21 @@ Examples:
|
|
|
494
818
|
|
|
495
819
|
# Handle query or interactive mode
|
|
496
820
|
async def run_cli():
|
|
497
|
-
|
|
821
|
+
cli_instance = NocturnalCLI()
|
|
498
822
|
|
|
499
823
|
if args.query and not args.interactive:
|
|
500
|
-
|
|
824
|
+
# Check if workflow flags are set
|
|
825
|
+
if args.save or args.copy or args.format:
|
|
826
|
+
await cli_instance.single_query_with_workflow(
|
|
827
|
+
args.query,
|
|
828
|
+
save_to_library=args.save,
|
|
829
|
+
copy_to_clipboard=args.copy,
|
|
830
|
+
export_format=args.format
|
|
831
|
+
)
|
|
832
|
+
else:
|
|
833
|
+
await cli_instance.single_query(args.query)
|
|
501
834
|
else:
|
|
502
|
-
await
|
|
835
|
+
await cli_instance.interactive_mode()
|
|
503
836
|
|
|
504
837
|
try:
|
|
505
838
|
asyncio.run(run_cli())
|