cite-agent 1.3.0__tar.gz → 1.3.1__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.
Files changed (94) hide show
  1. {cite_agent-1.3.0/cite_agent.egg-info → cite_agent-1.3.1}/PKG-INFO +1 -1
  2. cite_agent-1.3.1/cite_agent/__version__.py +1 -0
  3. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/enhanced_ai_agent.py +214 -173
  4. {cite_agent-1.3.0 → cite_agent-1.3.1/cite_agent.egg-info}/PKG-INFO +1 -1
  5. {cite_agent-1.3.0 → cite_agent-1.3.1}/setup.py +1 -1
  6. cite_agent-1.3.0/cite_agent/__version__.py +0 -1
  7. {cite_agent-1.3.0 → cite_agent-1.3.1}/LICENSE +0 -0
  8. {cite_agent-1.3.0 → cite_agent-1.3.1}/MANIFEST.in +0 -0
  9. {cite_agent-1.3.0 → cite_agent-1.3.1}/README.md +0 -0
  10. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/__init__.py +0 -0
  11. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/__main__.py +0 -0
  12. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/account_client.py +0 -0
  13. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/agent_backend_only.py +0 -0
  14. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/ascii_plotting.py +0 -0
  15. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/auth.py +0 -0
  16. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/backend_only_client.py +0 -0
  17. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/cli.py +0 -0
  18. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/cli_conversational.py +0 -0
  19. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/cli_enhanced.py +0 -0
  20. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/cli_workflow.py +0 -0
  21. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/dashboard.py +0 -0
  22. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/rate_limiter.py +0 -0
  23. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/session_manager.py +0 -0
  24. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/setup_config.py +0 -0
  25. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/streaming_ui.py +0 -0
  26. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/telemetry.py +0 -0
  27. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/ui.py +0 -0
  28. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/updater.py +0 -0
  29. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/web_search.py +0 -0
  30. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/workflow.py +0 -0
  31. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent/workflow_integration.py +0 -0
  32. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent.egg-info/SOURCES.txt +0 -0
  33. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent.egg-info/dependency_links.txt +0 -0
  34. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent.egg-info/entry_points.txt +0 -0
  35. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent.egg-info/requires.txt +0 -0
  36. {cite_agent-1.3.0 → cite_agent-1.3.1}/cite_agent.egg-info/top_level.txt +0 -0
  37. {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/BETA_LAUNCH_CHECKLIST.md +0 -0
  38. {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/BETA_RELEASE_CHECKLIST.md +0 -0
  39. {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/ENHANCED_CAPABILITIES.md +0 -0
  40. {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/GROQ_RATE_LIMITS.md +0 -0
  41. {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/INSTALL.md +0 -0
  42. {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/PUBLISHING_PYPI.md +0 -0
  43. {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/SECURE_PACKAGING_GUIDE.md +0 -0
  44. {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/SECURITY_AUDIT.md +0 -0
  45. {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/USER_GETTING_STARTED.md +0 -0
  46. {cite_agent-1.3.0 → cite_agent-1.3.1}/docs/playbooks/BETA_LAUNCH_PLAYBOOK.md +0 -0
  47. {cite_agent-1.3.0 → cite_agent-1.3.1}/requirements.txt +0 -0
  48. {cite_agent-1.3.0 → cite_agent-1.3.1}/setup.cfg +0 -0
  49. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/__init__.py +0 -0
  50. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/__init__.py +0 -0
  51. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/auth_service/__init__.py +0 -0
  52. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/auth_service/auth_manager.py +0 -0
  53. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/graph/__init__.py +0 -0
  54. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/graph/knowledge_graph.py +0 -0
  55. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/llm_service/__init__.py +0 -0
  56. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/llm_service/llm_manager.py +0 -0
  57. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/paper_service/__init__.py +0 -0
  58. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/paper_service/openalex.py +0 -0
  59. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/performance_service/__init__.py +0 -0
  60. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/performance_service/rust_performance.py +0 -0
  61. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/__init__.py +0 -0
  62. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/chatbot.py +0 -0
  63. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/citation_manager.py +0 -0
  64. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/context_manager.py +0 -0
  65. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/conversation_manager.py +0 -0
  66. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/critical_paper_detector.py +0 -0
  67. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/enhanced_research.py +0 -0
  68. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/enhanced_synthesizer.py +0 -0
  69. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/query_generator.py +0 -0
  70. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/research_service/synthesizer.py +0 -0
  71. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/search_service/__init__.py +0 -0
  72. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/search_service/indexer.py +0 -0
  73. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/search_service/search_engine.py +0 -0
  74. {cite_agent-1.3.0 → cite_agent-1.3.1}/src/services/simple_enhanced_main.py +0 -0
  75. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/beta_launch_test_suite.py +0 -0
  76. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/enhanced/test_account_client.py +0 -0
  77. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/enhanced/test_archive_agent.py +0 -0
  78. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/enhanced/test_enhanced_agent_runtime.py +0 -0
  79. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/enhanced/test_reasoning_engine.py +0 -0
  80. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/enhanced/test_setup_config.py +0 -0
  81. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/enhanced/test_tool_framework.py +0 -0
  82. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/integration_test.py +0 -0
  83. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/test_truth_seeking_comprehensive.py +0 -0
  84. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_accuracy_system.py +0 -0
  85. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_agent_live.py +0 -0
  86. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_backend_local.py +0 -0
  87. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_cerebras_comparison.py +0 -0
  88. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_improved_prompt.py +0 -0
  89. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_qualitative_robustness.py +0 -0
  90. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_qualitative_system.py +0 -0
  91. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_truth_seeking_chinese.py +0 -0
  92. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_truth_seeking_comprehensive.py +0 -0
  93. {cite_agent-1.3.0 → cite_agent-1.3.1}/tests/validation/test_truth_seeking_real.py +0 -0
  94. {cite_agent-1.3.0 → cite_agent-1.3.1}/tools/security_audit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cite-agent
3
- Version: 1.3.0
3
+ Version: 1.3.1
4
4
  Summary: Terminal AI assistant for academic research with citation verification
5
5
  Home-page: https://github.com/Spectating101/cite-agent
6
6
  Author: Cite-Agent Team
@@ -0,0 +1 @@
1
+ __version__ = "1.3.1"
@@ -2594,23 +2594,125 @@ class EnhancedNocturnalAgent:
2594
2594
  if workflow_response:
2595
2595
  return workflow_response
2596
2596
 
2597
- # Analyze request to determine what APIs to call
2598
- request_analysis = await self._analyze_request_type(request.question)
2599
-
2600
- # Debug: Check what was detected
2597
+ # Initialize
2598
+ api_results = {}
2599
+ tools_used = []
2601
2600
  debug_mode = os.getenv("NOCTURNAL_DEBUG", "").lower() == "1"
2601
+
2602
+ # ========================================================================
2603
+ # PRIORITY 1: SHELL PLANNING (Reasoning Layer - Runs FIRST for ALL modes)
2604
+ # ========================================================================
2605
+ # This determines USER INTENT before fetching any data
2606
+ # Prevents waste: "find cm522" won't trigger Archive API, "look into it" won't web search
2607
+ # Works in BOTH production and dev modes
2608
+
2609
+ shell_action = "none" # Will be: pwd|ls|find|none
2610
+
2611
+ # Quick check if query might need shell
2612
+ question_lower = request.question.lower()
2613
+ might_need_shell = any(word in question_lower for word in [
2614
+ 'directory', 'folder', 'where', 'find', 'list', 'files', 'look', 'search', 'check', 'into'
2615
+ ])
2616
+
2617
+ if might_need_shell and self.shell_session:
2618
+ # Ask LLM planner: What shell command should we run?
2619
+ planner_prompt = f"""You are a shell command planner. Determine what shell command to run.
2620
+
2621
+ User query: "{request.question}"
2622
+ Previous conversation: {json.dumps(self.conversation_history[-2:]) if self.conversation_history else "None"}
2623
+
2624
+ Respond ONLY with JSON:
2625
+ {{
2626
+ "action": "pwd|ls|find|none",
2627
+ "search_target": "cm522" (if find),
2628
+ "search_path": "~/Downloads" (if find),
2629
+ "target_path": "/full/path" (if ls on previous result)
2630
+ }}
2631
+
2632
+ Examples:
2633
+ "where am i?" → {{"action": "pwd"}}
2634
+ "what files here?" → {{"action": "ls"}}
2635
+ "find cm522 in downloads" → {{"action": "find", "search_target": "cm522", "search_path": "~/Downloads"}}
2636
+ "look into it" + Previous: "Found /path/to/dir" → {{"action": "ls", "target_path": "/path/to/dir"}}
2637
+ "Tesla revenue" → {{"action": "none"}}
2638
+
2639
+ JSON:"""
2640
+
2641
+ try:
2642
+ plan_response = await self.call_backend_query(
2643
+ query=planner_prompt,
2644
+ conversation_history=[],
2645
+ api_results={},
2646
+ tools_used=[]
2647
+ )
2648
+
2649
+ plan_text = plan_response.response.strip()
2650
+ if '```' in plan_text:
2651
+ plan_text = plan_text.split('```')[1].replace('json', '').strip()
2652
+
2653
+ plan = json.loads(plan_text)
2654
+ shell_action = plan.get("action", "none")
2655
+
2656
+ if debug_mode:
2657
+ print(f"🔍 SHELL PLAN: {plan}")
2658
+
2659
+ # Execute shell command based on plan
2660
+ if shell_action == "pwd":
2661
+ pwd_output = self.execute_command("pwd")
2662
+ api_results["shell_info"] = {"current_directory": pwd_output.strip()}
2663
+ tools_used.append("shell_execution")
2664
+
2665
+ elif shell_action == "ls":
2666
+ target = plan.get("target_path")
2667
+ if target:
2668
+ ls_output = self.execute_command(f"ls -lah {target}")
2669
+ api_results["shell_info"] = {
2670
+ "directory_contents": ls_output,
2671
+ "target_path": target
2672
+ }
2673
+ else:
2674
+ ls_output = self.execute_command("ls -lah")
2675
+ api_results["shell_info"] = {"directory_contents": ls_output}
2676
+ tools_used.append("shell_execution")
2677
+
2678
+ elif shell_action == "find":
2679
+ search_target = plan.get("search_target", "")
2680
+ search_path = plan.get("search_path", "~")
2681
+ if search_target:
2682
+ find_cmd = f"find {search_path} -maxdepth 4 -type d -iname '*{search_target}*' 2>/dev/null | head -20"
2683
+ find_output = self.execute_command(find_cmd)
2684
+ if debug_mode:
2685
+ print(f"🔍 FIND: {find_cmd}")
2686
+ print(f"🔍 OUTPUT: {repr(find_output)}")
2687
+ if find_output.strip():
2688
+ api_results["shell_info"] = {
2689
+ "search_results": f"Searched for '*{search_target}*' in {search_path}:\n{find_output}"
2690
+ }
2691
+ else:
2692
+ api_results["shell_info"] = {
2693
+ "search_results": f"No directories matching '{search_target}' found in {search_path}"
2694
+ }
2695
+ tools_used.append("shell_execution")
2696
+
2697
+ except Exception as e:
2698
+ if debug_mode:
2699
+ print(f"🔍 Shell planner failed: {e}, continuing without shell")
2700
+ shell_action = "none"
2701
+
2702
+ # ========================================================================
2703
+ # PRIORITY 2: DATA APIs (Only if shell didn't fully handle the query)
2704
+ # ========================================================================
2705
+ # If shell_action = pwd/ls/find, we might still want data APIs
2706
+ # But we skip vague queries to save tokens
2707
+
2708
+ # Analyze what data APIs are needed (only if not pure shell command)
2709
+ request_analysis = await self._analyze_request_type(request.question)
2602
2710
  if debug_mode:
2603
2711
  print(f"🔍 Request analysis: {request_analysis}")
2604
2712
 
2605
- # Check if query is too vague - skip EXPENSIVE API calls to save tokens
2606
- # But still allow web search (cheap and flexible)
2607
2713
  is_vague = self._is_query_too_vague_for_apis(request.question)
2608
2714
  if debug_mode and is_vague:
2609
- print(f"🔍 Query detected as VAGUE - skipping Archive/FinSight, but may use web search")
2610
-
2611
- # Call appropriate APIs (Archive, FinSight) - BOTH production and dev mode
2612
- api_results = {}
2613
- tools_used = []
2715
+ print(f"🔍 Query is VAGUE - skipping expensive APIs")
2614
2716
 
2615
2717
  # Skip Archive/FinSight if query is too vague, but still allow web search later
2616
2718
  if not is_vague:
@@ -2629,202 +2731,141 @@ class EnhancedNocturnalAgent:
2629
2731
  api_results["research"] = result
2630
2732
  tools_used.append("archive_api")
2631
2733
 
2632
- # FinSight API for financial data
2734
+ # FinSight API for financial data - Use LLM for ticker/metric extraction
2633
2735
  if "finsight" in request_analysis.get("apis", []):
2634
- tickers = self._extract_tickers_from_text(request.question)
2635
- if not tickers:
2636
- # Try common company name mappings
2637
- question_lower = request.question.lower()
2638
- if "apple" in question_lower:
2639
- tickers = ["AAPL"]
2640
- elif "tesla" in question_lower:
2641
- tickers = ["TSLA"]
2642
- elif "microsoft" in question_lower:
2643
- tickers = ["MSFT"]
2644
- elif "google" in question_lower or "alphabet" in question_lower:
2645
- tickers = ["GOOGL"]
2646
-
2647
- if debug_mode:
2648
- print(f"🔍 Extracted tickers: {tickers}")
2649
-
2650
- if tickers:
2651
- # Detect what metric user is asking for
2652
- question_lower = request.question.lower()
2653
- metric = "revenue" # Default
2654
-
2655
- if any(word in question_lower for word in ['market cap', 'marketcap', 'market value', 'valuation']):
2656
- metric = "marketCap"
2657
- elif any(word in question_lower for word in ['stock price', 'share price', 'current price', 'trading at']):
2658
- metric = "price"
2659
- elif 'profit' in question_lower and 'gross' not in question_lower:
2660
- metric = "netIncome"
2661
- elif 'earnings' in question_lower or 'eps' in question_lower:
2662
- metric = "eps"
2663
- elif any(word in question_lower for word in ['cash flow', 'cashflow']):
2664
- metric = "freeCashFlow"
2665
-
2666
- # Call FinSight with detected metric
2667
- if debug_mode:
2668
- print(f"🔍 Calling FinSight API: calc/{tickers[0]}/{metric}")
2669
- financial_data = await self._call_finsight_api(f"calc/{tickers[0]}/{metric}")
2670
- if debug_mode:
2671
- print(f"🔍 FinSight returned: {list(financial_data.keys()) if financial_data else None}")
2672
- if financial_data and "error" not in financial_data:
2673
- api_results["financial"] = financial_data
2674
- tools_used.append("finsight_api")
2675
- else:
2676
- if debug_mode and financial_data:
2677
- print(f"🔍 FinSight error: {financial_data.get('error')}")
2678
-
2679
- # Web Search fallback - ALWAYS available even for vague queries
2680
- # Use for: market share, industry data, current events, prices, anything not in APIs
2681
- if self.web_search:
2682
- question_lower = request.question.lower()
2683
- # Only search if query needs data and APIs didn't provide it
2684
- needs_web_search = (
2685
- ('market share' in question_lower) or
2686
- ('market size' in question_lower) or
2687
- ('industry' in question_lower and not api_results.get('research')) or
2688
- ('price' in question_lower and ('today' in question_lower or 'current' in question_lower or 'now' in question_lower)) or
2689
- ('bitcoin' in question_lower or 'btc' in question_lower or 'crypto' in question_lower) or
2690
- ('exchange rate' in question_lower or 'forex' in question_lower) or
2691
- (not api_results and 'latest' in question_lower) # Latest news/data
2692
- )
2693
-
2694
- if needs_web_search or (not api_results and len(request.question.split()) > 5):
2695
- try:
2696
- if debug_mode:
2697
- print(f"🔍 Using web search for: {request.question[:50]}...")
2698
- web_results = await self.web_search.search_web(request.question, num_results=3)
2699
- if web_results and "results" in web_results:
2700
- api_results["web_search"] = web_results
2701
- tools_used.append("web_search")
2702
- if debug_mode:
2703
- print(f"🔍 Web search returned: {len(web_results.get('results', []))} results")
2704
- except Exception as e:
2705
- if debug_mode:
2706
- print(f"🔍 Web search failed: {e}")
2707
-
2708
- # PRODUCTION MODE: Use small LLM to plan shell commands (smarter than hardcoded patterns)
2709
- if self.client is None:
2710
- # Ask small LLM: What shell command should we run?
2711
- planner_prompt = f"""You are a shell command planner. Given the user's query, determine what shell command(s) to run.
2736
+ # LLM extracts ticker + metric (more accurate than regex)
2737
+ finance_prompt = f"""Extract financial query details from user's question.
2712
2738
 
2713
2739
  User query: "{request.question}"
2714
- Previous conversation context: {json.dumps(self.conversation_history[-2:]) if self.conversation_history else "None"}
2715
2740
 
2716
2741
  Respond with JSON:
2717
2742
  {{
2718
- "action": "pwd|ls|find|none",
2719
- "search_target": "directory/file name to search for (if action=find)",
2720
- "search_path": "~/Downloads or ~/Documents or ~ (if action=find)",
2721
- "target_path": "/full/path (if referring to previous result)"
2743
+ "tickers": ["AAPL", "TSLA"] (stock symbols - infer from company names if needed),
2744
+ "metric": "revenue|marketCap|price|netIncome|eps|freeCashFlow|grossProfit"
2722
2745
  }}
2723
2746
 
2724
2747
  Examples:
2725
- Query: "where am i?" → {{"action": "pwd"}}
2726
- Query: "what files are here?" → {{"action": "ls"}}
2727
- Query: "find cm522 in downloads" → {{"action": "find", "search_target": "cm522", "search_path": "~/Downloads"}}
2728
- Query: "can you look into it?" + Previous: "Found /home/user/Downloads/cm522-main" → {{"action": "ls", "target_path": "/home/user/Downloads/cm522-main"}}
2729
- Query: "what's the capital of France?" → {{"action": "none"}}
2748
+ - "Tesla revenue" → {{"tickers": ["TSLA"], "metric": "revenue"}}
2749
+ - "What's Apple worth?" → {{"tickers": ["AAPL"], "metric": "marketCap"}}
2750
+ - "tsla stock price" → {{"tickers": ["TSLA"], "metric": "price"}}
2751
+ - "Microsoft profit" → {{"tickers": ["MSFT"], "metric": "netIncome"}}
2730
2752
 
2731
2753
  JSON:"""
2732
2754
 
2733
- question_lower = request.question.lower()
2734
- needs_shell = any(word in question_lower for word in [
2735
- 'directory', 'folder', 'where', 'find', 'list', 'files', 'look', 'search', 'check'
2736
- ])
2737
-
2738
- if needs_shell and self.shell_session:
2739
2755
  try:
2740
- # Use small LLM to plan shell command (smarter than regex)
2741
- plan_response = await self.call_backend_query(
2742
- query=planner_prompt,
2756
+ finance_response = await self.call_backend_query(
2757
+ query=finance_prompt,
2743
2758
  conversation_history=[],
2744
2759
  api_results={},
2745
2760
  tools_used=[]
2746
2761
  )
2747
2762
 
2748
- # Parse JSON plan
2749
2763
  import json as json_module
2750
- plan_text = plan_response.response.strip()
2751
- # Extract JSON if wrapped in markdown
2752
- if '```' in plan_text:
2753
- plan_text = plan_text.split('```')[1].replace('json', '').strip()
2764
+ finance_text = finance_response.response.strip()
2765
+ if '```' in finance_text:
2766
+ finance_text = finance_text.split('```')[1].replace('json', '').strip()
2754
2767
 
2755
- plan = json_module.loads(plan_text)
2768
+ finance_plan = json_module.loads(finance_text)
2769
+ tickers = finance_plan.get("tickers", [])
2770
+ metric = finance_plan.get("metric", "revenue")
2756
2771
 
2757
- debug_mode = os.getenv("NOCTURNAL_DEBUG", "").lower() == "1"
2758
2772
  if debug_mode:
2759
- print(f"🔍 LLM PLAN: {plan}")
2760
-
2761
- api_results["shell_info"] = {}
2762
-
2763
- # Execute based on plan
2764
- action = plan.get("action", "none")
2773
+ print(f"🔍 LLM FINANCE PLAN: tickers={tickers}, metric={metric}")
2765
2774
 
2766
- if action == "pwd":
2767
- pwd_output = self.execute_command("pwd")
2768
- api_results["shell_info"]["current_directory"] = pwd_output.strip()
2769
- tools_used.append("shell_execution")
2770
-
2771
- elif action == "ls":
2772
- target = plan.get("target_path")
2773
- if target:
2774
- # List specific directory
2775
- ls_output = self.execute_command(f"ls -lah {target}")
2776
- else:
2777
- # List current directory
2778
- ls_output = self.execute_command("ls -lah")
2779
- api_results["shell_info"]["directory_contents"] = ls_output
2780
- if target:
2781
- api_results["shell_info"]["target_path"] = target
2782
- tools_used.append("shell_execution")
2783
-
2784
- elif action == "find":
2785
- search_target = plan.get("search_target", "")
2786
- search_path = plan.get("search_path", "~")
2787
-
2788
- if search_target:
2789
- find_cmd = f"find {search_path} -maxdepth 4 -type d -iname '*{search_target}*' 2>/dev/null | head -20"
2790
- find_output = self.execute_command(find_cmd)
2791
-
2792
- if debug_mode:
2793
- print(f"🔍 FIND EXECUTED: {find_cmd}")
2794
- print(f"🔍 FIND OUTPUT: {repr(find_output)}")
2795
-
2796
- if find_output.strip():
2797
- api_results["shell_info"]["search_results"] = f"Searched for '*{search_target}*' in {search_path}:\n{find_output}"
2798
- else:
2799
- api_results["shell_info"]["search_results"] = f"No directories matching '{search_target}' found in {search_path}"
2800
- tools_used.append("shell_execution")
2801
-
2802
- # Always include current directory
2803
- pwd_output = self.execute_command("pwd")
2804
- api_results["shell_info"]["current_directory"] = pwd_output.strip()
2775
+ if tickers:
2776
+ # Call FinSight with extracted ticker + metric
2777
+ financial_data = await self._call_finsight_api(f"calc/{tickers[0]}/{metric}")
2778
+ if debug_mode:
2779
+ print(f"🔍 FinSight returned: {list(financial_data.keys()) if financial_data else None}")
2780
+ if financial_data and "error" not in financial_data:
2781
+ api_results["financial"] = financial_data
2782
+ tools_used.append("finsight_api")
2805
2783
 
2806
2784
  except Exception as e:
2807
- # LLM planner failed, skip shell execution
2808
- debug_mode = os.getenv("NOCTURNAL_DEBUG", "").lower() == "1"
2809
2785
  if debug_mode:
2810
- print(f"🔍 Shell planner failed: {e}")
2811
-
2786
+ print(f"🔍 Finance LLM extraction failed: {e}")
2787
+
2788
+ # ========================================================================
2789
+ # PRIORITY 3: WEB SEARCH (Fallback - only if shell didn't handle AND no data yet)
2790
+ # ========================================================================
2791
+ # Only web search if:
2792
+ # - Shell said "none" (not a directory/file operation)
2793
+ # - We don't have enough data from Archive/FinSight
2794
+
2795
+ if self.web_search and shell_action == "none":
2796
+ # Ask LLM: Should we web search for this?
2797
+ web_decision_prompt = f"""Should we use web search for this query?
2798
+
2799
+ User query: "{request.question}"
2800
+ Data already available: {list(api_results.keys())}
2801
+ Shell action: {shell_action}
2802
+
2803
+ Respond with JSON:
2804
+ {{
2805
+ "use_web_search": true/false,
2806
+ "reason": "why or why not"
2807
+ }}
2808
+
2809
+ Use web search for:
2810
+ - Market share/size (not in SEC filings)
2811
+ - Current prices (Bitcoin, commodities, real-time data)
2812
+ - Industry data, statistics
2813
+ - Recent events, news
2814
+ - Questions not answered by existing data
2815
+
2816
+ Don't use if:
2817
+ - Shell already handled it (pwd/ls/find)
2818
+ - Question answered by research/financial APIs
2819
+ - Pure opinion question
2820
+
2821
+ JSON:"""
2822
+
2823
+ try:
2824
+ web_decision_response = await self.call_backend_query(
2825
+ query=web_decision_prompt,
2826
+ conversation_history=[],
2827
+ api_results={},
2828
+ tools_used=[]
2829
+ )
2830
+
2831
+ import json as json_module
2832
+ decision_text = web_decision_response.response.strip()
2833
+ if '```' in decision_text:
2834
+ decision_text = decision_text.split('```')[1].replace('json', '').strip()
2835
+
2836
+ decision = json_module.loads(decision_text)
2837
+ needs_web_search = decision.get("use_web_search", False)
2838
+
2839
+ if debug_mode:
2840
+ print(f"🔍 WEB SEARCH DECISION: {needs_web_search}, reason: {decision.get('reason')}")
2841
+
2842
+ if needs_web_search:
2843
+ web_results = await self.web_search.search_web(request.question, num_results=3)
2844
+ if web_results and "results" in web_results:
2845
+ api_results["web_search"] = web_results
2846
+ tools_used.append("web_search")
2847
+ if debug_mode:
2848
+ print(f"🔍 Web search returned: {len(web_results.get('results', []))} results")
2812
2849
 
2813
- # DEBUG: Log exactly what we're sending to backend
2814
- debug_mode = os.getenv("NOCTURNAL_DEBUG", "").lower() == "1"
2815
- if debug_mode and api_results.get("shell_info", {}).get("search_results"):
2816
- print(f"🔍 SENDING TO BACKEND:")
2817
- print(f"🔍 shell_info.search_results = {repr(api_results['shell_info']['search_results'])}")
2850
+ except Exception as e:
2851
+ if debug_mode:
2852
+ print(f"🔍 Web search decision failed: {e}")
2853
+
2854
+ # PRODUCTION MODE: Call backend LLM with all gathered data
2855
+ if self.client is None:
2856
+ # DEBUG: Log what we're sending
2857
+ if debug_mode and api_results.get("shell_info"):
2858
+ print(f"🔍 SENDING TO BACKEND: shell_info keys = {list(api_results.get('shell_info', {}).keys())}")
2818
2859
 
2819
2860
  # Call backend and UPDATE CONVERSATION HISTORY
2820
2861
  response = await self.call_backend_query(
2821
2862
  query=request.question,
2822
2863
  conversation_history=self.conversation_history[-10:],
2823
- api_results=api_results, # Include the data!
2824
- tools_used=tools_used # Pass tools list for history
2864
+ api_results=api_results,
2865
+ tools_used=tools_used
2825
2866
  )
2826
2867
 
2827
- # CRITICAL: Save to conversation history for context
2868
+ # CRITICAL: Save to conversation history
2828
2869
  self.conversation_history.append({"role": "user", "content": request.question})
2829
2870
  self.conversation_history.append({"role": "assistant", "content": response.response})
2830
2871
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cite-agent
3
- Version: 1.3.0
3
+ Version: 1.3.1
4
4
  Summary: Terminal AI assistant for academic research with citation verification
5
5
  Home-page: https://github.com/Spectating101/cite-agent
6
6
  Author: Cite-Agent Team
@@ -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.0",
10
+ version="1.3.1",
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.0"
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