cite-agent 1.3.4__tar.gz → 1.3.6__tar.gz
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-1.3.4/cite_agent.egg-info → cite_agent-1.3.6}/PKG-INFO +1 -1
- cite_agent-1.3.6/cite_agent/__version__.py +1 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/cli.py +13 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/enhanced_ai_agent.py +122 -8
- cite_agent-1.3.6/cite_agent/project_detector.py +148 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6/cite_agent.egg-info}/PKG-INFO +1 -1
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent.egg-info/SOURCES.txt +1 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/setup.py +1 -1
- cite_agent-1.3.4/cite_agent/__version__.py +0 -1
- {cite_agent-1.3.4 → cite_agent-1.3.6}/LICENSE +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/MANIFEST.in +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/README.md +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/__init__.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/__main__.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/account_client.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/agent_backend_only.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/ascii_plotting.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/auth.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/backend_only_client.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/cli_conversational.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/cli_enhanced.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/cli_workflow.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/dashboard.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/rate_limiter.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/session_manager.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/setup_config.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/streaming_ui.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/telemetry.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/ui.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/updater.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/web_search.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/workflow.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent/workflow_integration.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent.egg-info/dependency_links.txt +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent.egg-info/entry_points.txt +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent.egg-info/requires.txt +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/cite_agent.egg-info/top_level.txt +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/docs/BETA_LAUNCH_CHECKLIST.md +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/docs/BETA_RELEASE_CHECKLIST.md +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/docs/ENHANCED_CAPABILITIES.md +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/docs/GROQ_RATE_LIMITS.md +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/docs/INSTALL.md +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/docs/PUBLISHING_PYPI.md +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/docs/SECURE_PACKAGING_GUIDE.md +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/docs/SECURITY_AUDIT.md +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/docs/USER_GETTING_STARTED.md +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/docs/playbooks/BETA_LAUNCH_PLAYBOOK.md +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/requirements.txt +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/setup.cfg +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/__init__.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/__init__.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/auth_service/__init__.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/auth_service/auth_manager.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/graph/__init__.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/graph/knowledge_graph.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/llm_service/__init__.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/llm_service/llm_manager.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/paper_service/__init__.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/paper_service/openalex.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/performance_service/__init__.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/performance_service/rust_performance.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/research_service/__init__.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/research_service/chatbot.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/research_service/citation_manager.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/research_service/context_manager.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/research_service/conversation_manager.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/research_service/critical_paper_detector.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/research_service/enhanced_research.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/research_service/enhanced_synthesizer.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/research_service/query_generator.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/research_service/synthesizer.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/search_service/__init__.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/search_service/indexer.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/search_service/search_engine.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/simple_enhanced_main.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/beta_launch_test_suite.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/enhanced/test_account_client.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/enhanced/test_archive_agent.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/enhanced/test_enhanced_agent_runtime.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/enhanced/test_reasoning_engine.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/enhanced/test_setup_config.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/enhanced/test_tool_framework.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/integration_test.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/test_truth_seeking_comprehensive.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/validation/test_accuracy_system.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/validation/test_agent_live.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/validation/test_backend_local.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/validation/test_cerebras_comparison.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/validation/test_improved_prompt.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/validation/test_qualitative_robustness.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/validation/test_qualitative_system.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/validation/test_truth_seeking_chinese.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/validation/test_truth_seeking_comprehensive.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tests/validation/test_truth_seeking_real.py +0 -0
- {cite_agent-1.3.4 → cite_agent-1.3.6}/tools/security_audit.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.3.6"
|
|
@@ -256,6 +256,19 @@ class NocturnalCLI:
|
|
|
256
256
|
if not await self.initialize():
|
|
257
257
|
return
|
|
258
258
|
|
|
259
|
+
# Detect if user is in a project directory (R, Python, Node, Jupyter, etc.)
|
|
260
|
+
try:
|
|
261
|
+
from .project_detector import ProjectDetector
|
|
262
|
+
detector = ProjectDetector()
|
|
263
|
+
project_info = detector.detect_project()
|
|
264
|
+
|
|
265
|
+
if project_info:
|
|
266
|
+
# Show project banner
|
|
267
|
+
banner = detector.format_project_banner(project_info)
|
|
268
|
+
self.console.print(banner, style="dim")
|
|
269
|
+
except:
|
|
270
|
+
pass # Silently skip if detection fails
|
|
271
|
+
|
|
259
272
|
self.console.print("\n[bold]🤖 Interactive Mode[/] — Type your questions or 'quit' to exit")
|
|
260
273
|
self.console.rule(style="magenta")
|
|
261
274
|
|
|
@@ -1698,10 +1698,55 @@ class EnhancedNocturnalAgent:
|
|
|
1698
1698
|
|
|
1699
1699
|
elif response.status == 503:
|
|
1700
1700
|
# Backend AI service temporarily unavailable (Cerebras/Groq rate limited)
|
|
1701
|
-
#
|
|
1701
|
+
# Auto-retry silently with exponential backoff
|
|
1702
|
+
|
|
1703
|
+
print("\n💭 Thinking... (backend is busy, retrying automatically)")
|
|
1704
|
+
|
|
1705
|
+
import asyncio
|
|
1706
|
+
retry_delays = [5, 15, 30] # Exponential backoff
|
|
1707
|
+
|
|
1708
|
+
for retry_num, delay in enumerate(retry_delays):
|
|
1709
|
+
await asyncio.sleep(delay)
|
|
1710
|
+
|
|
1711
|
+
# Retry the request
|
|
1712
|
+
async with self.session.post(url, json=payload, headers=headers, timeout=60) as retry_response:
|
|
1713
|
+
if retry_response.status == 200:
|
|
1714
|
+
# Success!
|
|
1715
|
+
data = await retry_response.json()
|
|
1716
|
+
response_text = data.get('response', '')
|
|
1717
|
+
tokens = data.get('tokens_used', 0)
|
|
1718
|
+
|
|
1719
|
+
all_tools = tools_used or []
|
|
1720
|
+
all_tools.append("backend_llm")
|
|
1721
|
+
|
|
1722
|
+
self.workflow.save_query_result(
|
|
1723
|
+
query=query,
|
|
1724
|
+
response=response_text,
|
|
1725
|
+
metadata={
|
|
1726
|
+
"tools_used": all_tools,
|
|
1727
|
+
"tokens_used": tokens,
|
|
1728
|
+
"model": data.get('model'),
|
|
1729
|
+
"provider": data.get('provider'),
|
|
1730
|
+
"retries": retry_num + 1
|
|
1731
|
+
}
|
|
1732
|
+
)
|
|
1733
|
+
|
|
1734
|
+
return ChatResponse(
|
|
1735
|
+
response=response_text,
|
|
1736
|
+
tokens_used=tokens,
|
|
1737
|
+
tools_used=all_tools,
|
|
1738
|
+
model=data.get('model', 'llama-3.3-70b'),
|
|
1739
|
+
timestamp=data.get('timestamp', datetime.now(timezone.utc).isoformat()),
|
|
1740
|
+
api_results=api_results
|
|
1741
|
+
)
|
|
1742
|
+
elif retry_response.status != 503:
|
|
1743
|
+
# Different error, stop retrying
|
|
1744
|
+
break
|
|
1745
|
+
|
|
1746
|
+
# All retries exhausted
|
|
1702
1747
|
return ChatResponse(
|
|
1703
|
-
response="
|
|
1704
|
-
error_message="Service
|
|
1748
|
+
response="❌ Service unavailable. Please try again in a few minutes.",
|
|
1749
|
+
error_message="Service unavailable after retries"
|
|
1705
1750
|
)
|
|
1706
1751
|
|
|
1707
1752
|
elif response.status == 200:
|
|
@@ -2619,7 +2664,8 @@ class EnhancedNocturnalAgent:
|
|
|
2619
2664
|
# Quick check if query might need shell
|
|
2620
2665
|
question_lower = request.question.lower()
|
|
2621
2666
|
might_need_shell = any(word in question_lower for word in [
|
|
2622
|
-
'directory', 'folder', 'where', 'find', 'list', 'files', 'look', 'search', 'check', 'into'
|
|
2667
|
+
'directory', 'folder', 'where', 'find', 'list', 'files', 'look', 'search', 'check', 'into',
|
|
2668
|
+
'show', 'open', 'read', 'display', 'cat', 'view', 'contents', '.r', '.py', '.csv', '.ipynb'
|
|
2623
2669
|
])
|
|
2624
2670
|
|
|
2625
2671
|
if might_need_shell and self.shell_session:
|
|
@@ -2631,19 +2677,28 @@ Previous conversation: {json.dumps(self.conversation_history[-2:]) if self.conve
|
|
|
2631
2677
|
|
|
2632
2678
|
Respond ONLY with JSON:
|
|
2633
2679
|
{{
|
|
2634
|
-
"action": "pwd|ls|find|none",
|
|
2680
|
+
"action": "pwd|ls|find|read_file|none",
|
|
2635
2681
|
"search_target": "cm522" (if find),
|
|
2636
2682
|
"search_path": "~/Downloads" (if find),
|
|
2637
|
-
"target_path": "/full/path" (if ls on previous result)
|
|
2683
|
+
"target_path": "/full/path" (if ls on previous result),
|
|
2684
|
+
"file_path": "/full/path/to/file.R" (if read_file)
|
|
2638
2685
|
}}
|
|
2639
2686
|
|
|
2640
2687
|
Examples:
|
|
2641
2688
|
"where am i?" → {{"action": "pwd"}}
|
|
2642
2689
|
"what files here?" → {{"action": "ls"}}
|
|
2643
|
-
"find cm522
|
|
2644
|
-
"look into it" + Previous: "Found /path
|
|
2690
|
+
"find cm522" → {{"action": "find", "search_target": "cm522"}}
|
|
2691
|
+
"look into it" + Previous: "Found /path" → {{"action": "ls", "target_path": "/path"}}
|
|
2692
|
+
"show me calculate_betas.R" → {{"action": "read_file", "file_path": "calculate_betas.R"}}
|
|
2693
|
+
"open regression.R" → {{"action": "read_file", "file_path": "regression.R"}}
|
|
2694
|
+
"read that file" + Previous: "regression.R" → {{"action": "read_file", "file_path": "regression.R"}}
|
|
2695
|
+
"display analysis.py" → {{"action": "read_file", "file_path": "analysis.py"}}
|
|
2696
|
+
"cat data.csv" → {{"action": "read_file", "file_path": "data.csv"}}
|
|
2697
|
+
"what columns does it have?" + Previous: file was shown → {{"action": "none"}} (LLM will parse from conversation)
|
|
2645
2698
|
"Tesla revenue" → {{"action": "none"}}
|
|
2646
2699
|
|
|
2700
|
+
KEY: If query mentions a specific FILENAME (*.R, *.py, *.csv), use read_file, NOT find!
|
|
2701
|
+
|
|
2647
2702
|
JSON:"""
|
|
2648
2703
|
|
|
2649
2704
|
try:
|
|
@@ -2701,6 +2756,65 @@ JSON:"""
|
|
|
2701
2756
|
"search_results": f"No directories matching '{search_target}' found in {search_path}"
|
|
2702
2757
|
}
|
|
2703
2758
|
tools_used.append("shell_execution")
|
|
2759
|
+
|
|
2760
|
+
elif shell_action == "read_file":
|
|
2761
|
+
# NEW: Read and inspect file (R, Python, CSV, etc.)
|
|
2762
|
+
import re # Import at function level
|
|
2763
|
+
|
|
2764
|
+
file_path = plan.get("file_path", "")
|
|
2765
|
+
if not file_path and might_need_shell:
|
|
2766
|
+
# Try to infer from query (e.g., "show me calculate_betas.R")
|
|
2767
|
+
filenames = re.findall(r'([a-zA-Z0-9_-]+\.[a-zA-Z]{1,4})', request.question)
|
|
2768
|
+
if filenames:
|
|
2769
|
+
# Check if file exists in current directory
|
|
2770
|
+
pwd = self.execute_command("pwd").strip()
|
|
2771
|
+
file_path = f"{pwd}/{filenames[0]}"
|
|
2772
|
+
|
|
2773
|
+
if file_path:
|
|
2774
|
+
if debug_mode:
|
|
2775
|
+
print(f"🔍 READING FILE: {file_path}")
|
|
2776
|
+
|
|
2777
|
+
# Read file content (first 100 lines to detect structure)
|
|
2778
|
+
cat_output = self.execute_command(f"head -100 {file_path}")
|
|
2779
|
+
|
|
2780
|
+
if not cat_output.startswith("ERROR"):
|
|
2781
|
+
# Detect file type and extract structure
|
|
2782
|
+
file_ext = file_path.split('.')[-1].lower()
|
|
2783
|
+
|
|
2784
|
+
# Extract column/variable info based on file type
|
|
2785
|
+
columns_info = ""
|
|
2786
|
+
if file_ext in ['csv', 'tsv']:
|
|
2787
|
+
# CSV: first line is usually headers
|
|
2788
|
+
first_line = cat_output.split('\n')[0] if cat_output else ""
|
|
2789
|
+
columns_info = f"CSV columns: {first_line}"
|
|
2790
|
+
elif file_ext in ['r', 'rmd']:
|
|
2791
|
+
# R script: look for dataframe column references (df$columnname)
|
|
2792
|
+
column_refs = re.findall(r'\$(\w+)', cat_output)
|
|
2793
|
+
unique_cols = list(dict.fromkeys(column_refs))[:10]
|
|
2794
|
+
if unique_cols:
|
|
2795
|
+
columns_info = f"Detected columns/variables: {', '.join(unique_cols)}"
|
|
2796
|
+
elif file_ext == 'py':
|
|
2797
|
+
# Python: look for DataFrame['column'] or df.column
|
|
2798
|
+
column_refs = re.findall(r'\[[\'""](\w+)[\'"]\]|\.(\w+)', cat_output)
|
|
2799
|
+
unique_cols = list(dict.fromkeys([c[0] or c[1] for c in column_refs if c[0] or c[1]]))[:10]
|
|
2800
|
+
if unique_cols:
|
|
2801
|
+
columns_info = f"Detected columns/attributes: {', '.join(unique_cols)}"
|
|
2802
|
+
|
|
2803
|
+
api_results["file_context"] = {
|
|
2804
|
+
"file_path": file_path,
|
|
2805
|
+
"file_type": file_ext,
|
|
2806
|
+
"content_preview": cat_output[:2000], # First 2000 chars
|
|
2807
|
+
"structure": columns_info,
|
|
2808
|
+
"full_content": cat_output # Full content for analysis
|
|
2809
|
+
}
|
|
2810
|
+
tools_used.append("file_read")
|
|
2811
|
+
|
|
2812
|
+
if debug_mode:
|
|
2813
|
+
print(f"🔍 FILE STRUCTURE: {columns_info}")
|
|
2814
|
+
else:
|
|
2815
|
+
api_results["file_context"] = {
|
|
2816
|
+
"error": f"Could not read file: {file_path}"
|
|
2817
|
+
}
|
|
2704
2818
|
|
|
2705
2819
|
except Exception as e:
|
|
2706
2820
|
if debug_mode:
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Generic project detection - works with ANY IDE/project type
|
|
4
|
+
Not RStudio-specific - detects R, Python, Node, Jupyter, etc.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict, List, Optional, Any
|
|
10
|
+
import glob
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ProjectDetector:
|
|
14
|
+
"""Detects project type and provides context"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, working_dir: Optional[str] = None):
|
|
17
|
+
self.working_dir = Path(working_dir or os.getcwd())
|
|
18
|
+
|
|
19
|
+
def detect_project(self) -> Optional[Dict[str, Any]]:
|
|
20
|
+
"""
|
|
21
|
+
Detect what kind of project user is working in
|
|
22
|
+
Returns project info or None if not in a project
|
|
23
|
+
"""
|
|
24
|
+
project_info = {
|
|
25
|
+
"type": None,
|
|
26
|
+
"name": None,
|
|
27
|
+
"recent_files": [],
|
|
28
|
+
"description": None
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Check for R project (.Rproj file OR 2+ .R files)
|
|
32
|
+
rproj_files = list(self.working_dir.glob("*.Rproj"))
|
|
33
|
+
r_files = list(self.working_dir.glob("*.R")) + list(self.working_dir.glob("*.Rmd"))
|
|
34
|
+
|
|
35
|
+
if rproj_files:
|
|
36
|
+
project_info["type"] = "R"
|
|
37
|
+
project_info["name"] = rproj_files[0].stem
|
|
38
|
+
project_info["recent_files"] = self._get_recent_files([".R", ".Rmd", ".qmd"])
|
|
39
|
+
project_info["description"] = f"R/RStudio project: {project_info['name']}"
|
|
40
|
+
return project_info
|
|
41
|
+
elif len(r_files) >= 2:
|
|
42
|
+
# R project without .Rproj file
|
|
43
|
+
project_info["type"] = "R"
|
|
44
|
+
project_info["name"] = self.working_dir.name
|
|
45
|
+
project_info["recent_files"] = self._get_recent_files([".R", ".Rmd", ".qmd"])
|
|
46
|
+
project_info["description"] = f"R project: {project_info['name']}"
|
|
47
|
+
return project_info
|
|
48
|
+
|
|
49
|
+
# Check for Python project
|
|
50
|
+
if (self.working_dir / "pyproject.toml").exists() or \
|
|
51
|
+
(self.working_dir / "setup.py").exists() or \
|
|
52
|
+
(self.working_dir / "requirements.txt").exists():
|
|
53
|
+
project_info["type"] = "Python"
|
|
54
|
+
project_info["name"] = self.working_dir.name
|
|
55
|
+
project_info["recent_files"] = self._get_recent_files([".py", ".ipynb"])
|
|
56
|
+
project_info["description"] = f"Python project: {project_info['name']}"
|
|
57
|
+
return project_info
|
|
58
|
+
|
|
59
|
+
# Check for Node.js project
|
|
60
|
+
if (self.working_dir / "package.json").exists():
|
|
61
|
+
project_info["type"] = "Node"
|
|
62
|
+
project_info["name"] = self.working_dir.name
|
|
63
|
+
project_info["recent_files"] = self._get_recent_files([".js", ".ts", ".jsx", ".tsx"])
|
|
64
|
+
project_info["description"] = f"Node.js project: {project_info['name']}"
|
|
65
|
+
return project_info
|
|
66
|
+
|
|
67
|
+
# Check for Jupyter/Data Science directory
|
|
68
|
+
ipynb_files = list(self.working_dir.glob("*.ipynb"))
|
|
69
|
+
if len(ipynb_files) >= 2: # 2+ notebooks = likely data science project
|
|
70
|
+
project_info["type"] = "Jupyter"
|
|
71
|
+
project_info["name"] = self.working_dir.name
|
|
72
|
+
project_info["recent_files"] = self._get_recent_files([".ipynb", ".py", ".csv"])
|
|
73
|
+
project_info["description"] = f"Jupyter/Data Science project: {project_info['name']}"
|
|
74
|
+
return project_info
|
|
75
|
+
|
|
76
|
+
# Check for Git repository
|
|
77
|
+
if (self.working_dir / ".git").exists():
|
|
78
|
+
project_info["type"] = "Git"
|
|
79
|
+
project_info["name"] = self.working_dir.name
|
|
80
|
+
# Get recent files of any code type
|
|
81
|
+
project_info["recent_files"] = self._get_recent_files([".py", ".js", ".R", ".java", ".cpp", ".rs"])
|
|
82
|
+
project_info["description"] = f"Git repository: {project_info['name']}"
|
|
83
|
+
return project_info
|
|
84
|
+
|
|
85
|
+
# Not in a recognized project
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
def _get_recent_files(self, extensions: List[str], limit: int = 5) -> List[Dict[str, Any]]:
|
|
89
|
+
"""Get recently modified files with given extensions"""
|
|
90
|
+
files = []
|
|
91
|
+
|
|
92
|
+
for ext in extensions:
|
|
93
|
+
for filepath in self.working_dir.glob(f"**/*{ext}"):
|
|
94
|
+
if filepath.is_file() and not any(part.startswith('.') for part in filepath.parts):
|
|
95
|
+
try:
|
|
96
|
+
mtime = filepath.stat().st_mtime
|
|
97
|
+
files.append({
|
|
98
|
+
"name": filepath.name,
|
|
99
|
+
"path": str(filepath),
|
|
100
|
+
"relative_path": str(filepath.relative_to(self.working_dir)),
|
|
101
|
+
"modified": mtime,
|
|
102
|
+
"extension": ext
|
|
103
|
+
})
|
|
104
|
+
except:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
# Sort by modification time, newest first
|
|
108
|
+
files.sort(key=lambda f: f["modified"], reverse=True)
|
|
109
|
+
return files[:limit]
|
|
110
|
+
|
|
111
|
+
def format_project_banner(self, project_info: Dict[str, Any]) -> str:
|
|
112
|
+
"""Format project info as a nice banner"""
|
|
113
|
+
if not project_info or not project_info["type"]:
|
|
114
|
+
return ""
|
|
115
|
+
|
|
116
|
+
icon_map = {
|
|
117
|
+
"R": "📊",
|
|
118
|
+
"Python": "🐍",
|
|
119
|
+
"Node": "📦",
|
|
120
|
+
"Jupyter": "📓",
|
|
121
|
+
"Git": "📂"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
icon = icon_map.get(project_info["type"], "📁")
|
|
125
|
+
|
|
126
|
+
banner = f"\n{icon} {project_info['description']}\n"
|
|
127
|
+
|
|
128
|
+
if project_info["recent_files"]:
|
|
129
|
+
banner += "📄 Recent files:\n"
|
|
130
|
+
for f in project_info["recent_files"][:3]:
|
|
131
|
+
banner += f" • {f['relative_path']}\n"
|
|
132
|
+
|
|
133
|
+
return banner
|
|
134
|
+
|
|
135
|
+
def get_project_context_for_llm(self, project_info: Dict[str, Any]) -> str:
|
|
136
|
+
"""Get project context to add to LLM prompts"""
|
|
137
|
+
if not project_info or not project_info["type"]:
|
|
138
|
+
return ""
|
|
139
|
+
|
|
140
|
+
context = f"User is working in a {project_info['type']} project: {project_info['name']}\n"
|
|
141
|
+
|
|
142
|
+
if project_info["recent_files"]:
|
|
143
|
+
context += "Recent files:\n"
|
|
144
|
+
for f in project_info["recent_files"][:5]:
|
|
145
|
+
context += f"- {f['relative_path']}\n"
|
|
146
|
+
|
|
147
|
+
return context
|
|
148
|
+
|
|
@@ -7,7 +7,7 @@ long_description = readme_path.read_text() if readme_path.exists() else "Termina
|
|
|
7
7
|
|
|
8
8
|
setup(
|
|
9
9
|
name="cite-agent",
|
|
10
|
-
version="1.3.
|
|
10
|
+
version="1.3.6",
|
|
11
11
|
author="Cite-Agent Team",
|
|
12
12
|
author_email="contact@citeagent.dev",
|
|
13
13
|
description="Terminal AI assistant for academic research with citation verification",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.3.4"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cite_agent-1.3.4 → cite_agent-1.3.6}/src/services/research_service/critical_paper_detector.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|