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.

Files changed (48) hide show
  1. cite_agent/__init__.py +1 -1
  2. cite_agent/account_client.py +19 -46
  3. cite_agent/agent_backend_only.py +30 -4
  4. cite_agent/cli.py +397 -64
  5. cite_agent/cli_conversational.py +294 -0
  6. cite_agent/cli_workflow.py +276 -0
  7. cite_agent/enhanced_ai_agent.py +3222 -117
  8. cite_agent/session_manager.py +215 -0
  9. cite_agent/setup_config.py +5 -21
  10. cite_agent/streaming_ui.py +252 -0
  11. cite_agent/updater.py +50 -17
  12. cite_agent/workflow.py +427 -0
  13. cite_agent/workflow_integration.py +275 -0
  14. cite_agent-1.2.3.dist-info/METADATA +442 -0
  15. cite_agent-1.2.3.dist-info/RECORD +54 -0
  16. {cite_agent-1.0.4.dist-info → cite_agent-1.2.3.dist-info}/top_level.txt +1 -0
  17. src/__init__.py +1 -0
  18. src/services/__init__.py +132 -0
  19. src/services/auth_service/__init__.py +3 -0
  20. src/services/auth_service/auth_manager.py +33 -0
  21. src/services/graph/__init__.py +1 -0
  22. src/services/graph/knowledge_graph.py +194 -0
  23. src/services/llm_service/__init__.py +5 -0
  24. src/services/llm_service/llm_manager.py +495 -0
  25. src/services/paper_service/__init__.py +5 -0
  26. src/services/paper_service/openalex.py +231 -0
  27. src/services/performance_service/__init__.py +1 -0
  28. src/services/performance_service/rust_performance.py +395 -0
  29. src/services/research_service/__init__.py +23 -0
  30. src/services/research_service/chatbot.py +2056 -0
  31. src/services/research_service/citation_manager.py +436 -0
  32. src/services/research_service/context_manager.py +1441 -0
  33. src/services/research_service/conversation_manager.py +597 -0
  34. src/services/research_service/critical_paper_detector.py +577 -0
  35. src/services/research_service/enhanced_research.py +121 -0
  36. src/services/research_service/enhanced_synthesizer.py +375 -0
  37. src/services/research_service/query_generator.py +777 -0
  38. src/services/research_service/synthesizer.py +1273 -0
  39. src/services/search_service/__init__.py +5 -0
  40. src/services/search_service/indexer.py +186 -0
  41. src/services/search_service/search_engine.py +342 -0
  42. src/services/simple_enhanced_main.py +287 -0
  43. cite_agent/__distribution__.py +0 -7
  44. cite_agent-1.0.4.dist-info/METADATA +0 -234
  45. cite_agent-1.0.4.dist-info/RECORD +0 -23
  46. {cite_agent-1.0.4.dist-info → cite_agent-1.2.3.dist-info}/WHEEL +0 -0
  47. {cite_agent-1.0.4.dist-info → cite_agent-1.2.3.dist-info}/entry_points.txt +0 -0
  48. {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
- Nocturnal Archive CLI - Command Line Interface
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 Nocturnal Archive"""
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
- async def initialize(self):
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
- self.console.print("\n[warning]👋 Hey there, looks like this machine hasn't met Nocturnal yet.[/warning]")
67
- self.console.print("[banner]Let's get you signed in — this only takes a minute.[/banner]")
68
- try:
69
- if not config.interactive_setup():
70
- self.console.print("[error]❌ Setup was cancelled. Exiting without starting the agent.[/error]")
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
- self._show_ready_panel()
94
- self._show_beta_banner()
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="🌙 Initializing Nocturnal Archive",
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="✅ Nocturnal Archive ready!",
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
- try:
150
- updater = NocturnalUpdater()
151
- update_info = updater.check_for_updates()
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
- request = ChatRequest(
246
- question=user_input,
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
- self.console.print(f"🤖 [bold]Processing[/]: {question}")
276
- self.console.rule(style="magenta")
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
- response = await self.agent.process_request(request)
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
- if response.tools_used:
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 whats working, what feels rough, or any paper/finance workflows you wish existed.\n"
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("# Nocturnal Archive Beta Feedback\n")
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="Nocturnal Archive - AI Research Assistant",
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-Agent v1.0.4")
446
- print("AI Research Assistant - Backend-Only Distribution")
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
- cli = NocturnalCLI()
821
+ cli_instance = NocturnalCLI()
498
822
 
499
823
  if args.query and not args.interactive:
500
- await cli.single_query(args.query)
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 cli.interactive_mode()
835
+ await cli_instance.interactive_mode()
503
836
 
504
837
  try:
505
838
  asyncio.run(run_cli())