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
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()
|
|
@@ -74,6 +101,10 @@ class NocturnalCLI:
|
|
|
74
101
|
self.console.print("[success]⚙️ Using saved credentials.[/success]")
|
|
75
102
|
else:
|
|
76
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
|
+
|
|
77
108
|
self.console.print("\n[warning]👋 Hey there, looks like this machine hasn't met Nocturnal yet.[/warning]")
|
|
78
109
|
self.console.print("[banner]Let's get you signed in — this only takes a minute.[/banner]")
|
|
79
110
|
try:
|
|
@@ -101,8 +132,10 @@ class NocturnalCLI:
|
|
|
101
132
|
self.console.print(" • Check your internet connection to the backend")
|
|
102
133
|
return False
|
|
103
134
|
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
106
139
|
return True
|
|
107
140
|
|
|
108
141
|
def _show_beta_banner(self):
|
|
@@ -131,13 +164,18 @@ class NocturnalCLI:
|
|
|
131
164
|
self.console.print(panel)
|
|
132
165
|
|
|
133
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
|
+
|
|
134
172
|
message = (
|
|
135
173
|
"Warming up your research cockpit…\n"
|
|
136
174
|
"[dim]Loading config, telemetry, and background update checks.[/dim]"
|
|
137
175
|
)
|
|
138
176
|
panel = Panel(
|
|
139
177
|
message,
|
|
140
|
-
title="
|
|
178
|
+
title="🤖 Initializing Cite Agent",
|
|
141
179
|
border_style="magenta",
|
|
142
180
|
padding=(1, 2),
|
|
143
181
|
box=box.ROUNDED,
|
|
@@ -148,7 +186,7 @@ class NocturnalCLI:
|
|
|
148
186
|
panel = Panel(
|
|
149
187
|
"Systems check complete.\n"
|
|
150
188
|
"Type [bold]help[/] for commands or [bold]tips[/] for power moves.",
|
|
151
|
-
title="✅
|
|
189
|
+
title="✅ Cite Agent ready!",
|
|
152
190
|
border_style="green",
|
|
153
191
|
padding=(1, 2),
|
|
154
192
|
box=box.ROUNDED,
|
|
@@ -230,27 +268,56 @@ class NocturnalCLI:
|
|
|
230
268
|
if user_input.lower() == 'feedback':
|
|
231
269
|
self.collect_feedback()
|
|
232
270
|
continue
|
|
233
|
-
|
|
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
|
+
|
|
234
286
|
if not user_input:
|
|
235
287
|
continue
|
|
236
288
|
except (EOFError, KeyboardInterrupt):
|
|
237
289
|
self.console.print("\n[warning]👋 Goodbye![/warning]")
|
|
238
290
|
break
|
|
239
291
|
|
|
240
|
-
self.console.print("[bold violet]🤖 Agent[/]: ", end="", highlight=False)
|
|
241
|
-
|
|
242
292
|
try:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
user_id="cli_user",
|
|
246
|
-
conversation_id=self.session_id
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
response = await self.agent.process_request(request)
|
|
293
|
+
from rich.spinner import Spinner
|
|
294
|
+
from rich.live import Live
|
|
250
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
|
+
|
|
251
306
|
# Print response with proper formatting
|
|
307
|
+
self.console.print("[bold violet]🤖 Agent[/]: ", end="", highlight=False)
|
|
252
308
|
self.console.print(response.response)
|
|
253
|
-
|
|
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
|
+
|
|
254
321
|
# Show usage stats occasionally
|
|
255
322
|
if hasattr(self.agent, 'daily_token_usage') and self.agent.daily_token_usage > 0:
|
|
256
323
|
stats = self.agent.get_usage_stats()
|
|
@@ -266,25 +333,26 @@ class NocturnalCLI:
|
|
|
266
333
|
|
|
267
334
|
async def single_query(self, question: str):
|
|
268
335
|
"""Process a single query"""
|
|
269
|
-
if not await self.initialize():
|
|
336
|
+
if not await self.initialize(non_interactive=True):
|
|
270
337
|
return
|
|
271
338
|
|
|
272
339
|
try:
|
|
273
|
-
|
|
274
|
-
|
|
340
|
+
from rich.spinner import Spinner
|
|
341
|
+
from rich.live import Live
|
|
275
342
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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)
|
|
283
352
|
|
|
284
353
|
self.console.print(f"\n📝 [bold]Response[/]:\n{response.response}")
|
|
285
354
|
|
|
286
|
-
|
|
287
|
-
self.console.print(f"\n🔧 Tools used: {', '.join(response.tools_used)}")
|
|
355
|
+
# Tools used removed for cleaner output
|
|
288
356
|
|
|
289
357
|
if response.tokens_used > 0:
|
|
290
358
|
stats = self.agent.get_usage_stats()
|
|
@@ -317,7 +385,7 @@ class NocturnalCLI:
|
|
|
317
385
|
"""Collect feedback from the user and store it locally"""
|
|
318
386
|
self.console.print(
|
|
319
387
|
Panel(
|
|
320
|
-
"Share what
|
|
388
|
+
"Share what's working, what feels rough, or any paper/finance workflows you wish existed.\n"
|
|
321
389
|
"Press Enter on an empty line to finish.",
|
|
322
390
|
title="📝 Beta Feedback",
|
|
323
391
|
border_style="cyan",
|
|
@@ -348,7 +416,7 @@ class NocturnalCLI:
|
|
|
348
416
|
|
|
349
417
|
content = "\n".join(lines)
|
|
350
418
|
with open(feedback_path, "w", encoding="utf-8") as handle:
|
|
351
|
-
handle.write("#
|
|
419
|
+
handle.write("# Cite Agent Feedback\n")
|
|
352
420
|
handle.write(f"timestamp = {timestamp}Z\n")
|
|
353
421
|
handle.write("\n")
|
|
354
422
|
handle.write(content)
|
|
@@ -360,10 +428,184 @@ class NocturnalCLI:
|
|
|
360
428
|
self.console.print("[dim]Attach that file when you send feedback to the team.[/dim]")
|
|
361
429
|
return 0
|
|
362
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
|
+
|
|
363
605
|
def main():
|
|
364
606
|
"""Main CLI entry point"""
|
|
365
607
|
parser = argparse.ArgumentParser(
|
|
366
|
-
description="
|
|
608
|
+
description="Cite Agent - AI Research Assistant with real data",
|
|
367
609
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
368
610
|
epilog="""
|
|
369
611
|
Examples:
|
|
@@ -419,10 +661,16 @@ Examples:
|
|
|
419
661
|
)
|
|
420
662
|
|
|
421
663
|
parser.add_argument(
|
|
422
|
-
'--feedback',
|
|
423
|
-
action='store_true',
|
|
664
|
+
'--feedback',
|
|
665
|
+
action='store_true',
|
|
424
666
|
help='Capture beta feedback and save it locally'
|
|
425
667
|
)
|
|
668
|
+
|
|
669
|
+
parser.add_argument(
|
|
670
|
+
'--workflow',
|
|
671
|
+
action='store_true',
|
|
672
|
+
help='Start workflow mode for integrated research management'
|
|
673
|
+
)
|
|
426
674
|
|
|
427
675
|
parser.add_argument(
|
|
428
676
|
'--import-secrets',
|
|
@@ -436,11 +684,66 @@ Examples:
|
|
|
436
684
|
help='Fail secret import if keyring is unavailable'
|
|
437
685
|
)
|
|
438
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
|
+
|
|
439
742
|
args = parser.parse_args()
|
|
440
743
|
|
|
441
744
|
# Handle version
|
|
442
745
|
if args.version:
|
|
443
|
-
print("
|
|
746
|
+
print("Cite Agent v1.2.2")
|
|
444
747
|
print("AI Research Assistant with real data integration")
|
|
445
748
|
return
|
|
446
749
|
|
|
@@ -454,6 +757,29 @@ Examples:
|
|
|
454
757
|
exit_code = cli.collect_feedback()
|
|
455
758
|
sys.exit(exit_code)
|
|
456
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
|
+
|
|
457
783
|
# Handle secret import before setup as it can be used non-interactively
|
|
458
784
|
if args.import_secrets:
|
|
459
785
|
config = NocturnalConfig()
|
|
@@ -492,12 +818,21 @@ Examples:
|
|
|
492
818
|
|
|
493
819
|
# Handle query or interactive mode
|
|
494
820
|
async def run_cli():
|
|
495
|
-
|
|
821
|
+
cli_instance = NocturnalCLI()
|
|
496
822
|
|
|
497
823
|
if args.query and not args.interactive:
|
|
498
|
-
|
|
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)
|
|
499
834
|
else:
|
|
500
|
-
await
|
|
835
|
+
await cli_instance.interactive_mode()
|
|
501
836
|
|
|
502
837
|
try:
|
|
503
838
|
asyncio.run(run_cli())
|