cite-agent 1.2.5__tar.gz → 1.2.7__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.

Files changed (95) hide show
  1. {cite_agent-1.2.5/cite_agent.egg-info → cite_agent-1.2.7}/PKG-INFO +28 -3
  2. {cite_agent-1.2.5 → cite_agent-1.2.7}/README.md +26 -2
  3. cite_agent-1.2.7/cite_agent/__main__.py +11 -0
  4. cite_agent-1.2.7/cite_agent/__version__.py +1 -0
  5. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/cli.py +19 -4
  6. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/enhanced_ai_agent.py +162 -73
  7. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/session_manager.py +30 -11
  8. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/web_search.py +18 -19
  9. {cite_agent-1.2.5 → cite_agent-1.2.7/cite_agent.egg-info}/PKG-INFO +28 -3
  10. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent.egg-info/SOURCES.txt +1 -0
  11. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent.egg-info/requires.txt +1 -0
  12. {cite_agent-1.2.5 → cite_agent-1.2.7}/setup.py +2 -1
  13. cite_agent-1.2.5/cite_agent/__version__.py +0 -1
  14. {cite_agent-1.2.5 → cite_agent-1.2.7}/LICENSE +0 -0
  15. {cite_agent-1.2.5 → cite_agent-1.2.7}/MANIFEST.in +0 -0
  16. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/__init__.py +0 -0
  17. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/account_client.py +0 -0
  18. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/agent_backend_only.py +0 -0
  19. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/ascii_plotting.py +0 -0
  20. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/auth.py +0 -0
  21. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/backend_only_client.py +0 -0
  22. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/cli_conversational.py +0 -0
  23. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/cli_enhanced.py +0 -0
  24. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/cli_workflow.py +0 -0
  25. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/dashboard.py +0 -0
  26. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/rate_limiter.py +0 -0
  27. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/setup_config.py +0 -0
  28. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/streaming_ui.py +0 -0
  29. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/telemetry.py +0 -0
  30. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/ui.py +0 -0
  31. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/updater.py +0 -0
  32. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/workflow.py +0 -0
  33. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent/workflow_integration.py +0 -0
  34. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent.egg-info/dependency_links.txt +0 -0
  35. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent.egg-info/entry_points.txt +0 -0
  36. {cite_agent-1.2.5 → cite_agent-1.2.7}/cite_agent.egg-info/top_level.txt +0 -0
  37. {cite_agent-1.2.5 → cite_agent-1.2.7}/docs/BETA_LAUNCH_CHECKLIST.md +0 -0
  38. {cite_agent-1.2.5 → cite_agent-1.2.7}/docs/BETA_RELEASE_CHECKLIST.md +0 -0
  39. {cite_agent-1.2.5 → cite_agent-1.2.7}/docs/ENHANCED_CAPABILITIES.md +0 -0
  40. {cite_agent-1.2.5 → cite_agent-1.2.7}/docs/GROQ_RATE_LIMITS.md +0 -0
  41. {cite_agent-1.2.5 → cite_agent-1.2.7}/docs/INSTALL.md +0 -0
  42. {cite_agent-1.2.5 → cite_agent-1.2.7}/docs/PUBLISHING_PYPI.md +0 -0
  43. {cite_agent-1.2.5 → cite_agent-1.2.7}/docs/SECURE_PACKAGING_GUIDE.md +0 -0
  44. {cite_agent-1.2.5 → cite_agent-1.2.7}/docs/SECURITY_AUDIT.md +0 -0
  45. {cite_agent-1.2.5 → cite_agent-1.2.7}/docs/USER_GETTING_STARTED.md +0 -0
  46. {cite_agent-1.2.5 → cite_agent-1.2.7}/docs/playbooks/BETA_LAUNCH_PLAYBOOK.md +0 -0
  47. {cite_agent-1.2.5 → cite_agent-1.2.7}/requirements-dev.txt +0 -0
  48. {cite_agent-1.2.5 → cite_agent-1.2.7}/requirements.txt +0 -0
  49. {cite_agent-1.2.5 → cite_agent-1.2.7}/setup.cfg +0 -0
  50. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/__init__.py +0 -0
  51. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/__init__.py +0 -0
  52. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/auth_service/__init__.py +0 -0
  53. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/auth_service/auth_manager.py +0 -0
  54. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/graph/__init__.py +0 -0
  55. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/graph/knowledge_graph.py +0 -0
  56. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/llm_service/__init__.py +0 -0
  57. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/llm_service/llm_manager.py +0 -0
  58. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/paper_service/__init__.py +0 -0
  59. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/paper_service/openalex.py +0 -0
  60. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/performance_service/__init__.py +0 -0
  61. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/performance_service/rust_performance.py +0 -0
  62. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/research_service/__init__.py +0 -0
  63. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/research_service/chatbot.py +0 -0
  64. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/research_service/citation_manager.py +0 -0
  65. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/research_service/context_manager.py +0 -0
  66. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/research_service/conversation_manager.py +0 -0
  67. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/research_service/critical_paper_detector.py +0 -0
  68. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/research_service/enhanced_research.py +0 -0
  69. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/research_service/enhanced_synthesizer.py +0 -0
  70. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/research_service/query_generator.py +0 -0
  71. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/research_service/synthesizer.py +0 -0
  72. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/search_service/__init__.py +0 -0
  73. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/search_service/indexer.py +0 -0
  74. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/search_service/search_engine.py +0 -0
  75. {cite_agent-1.2.5 → cite_agent-1.2.7}/src/services/simple_enhanced_main.py +0 -0
  76. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/beta_launch_test_suite.py +0 -0
  77. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/enhanced/test_account_client.py +0 -0
  78. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/enhanced/test_archive_agent.py +0 -0
  79. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/enhanced/test_enhanced_agent_runtime.py +0 -0
  80. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/enhanced/test_reasoning_engine.py +0 -0
  81. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/enhanced/test_setup_config.py +0 -0
  82. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/enhanced/test_tool_framework.py +0 -0
  83. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/integration_test.py +0 -0
  84. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/test_truth_seeking_comprehensive.py +0 -0
  85. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/validation/test_accuracy_system.py +0 -0
  86. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/validation/test_agent_live.py +0 -0
  87. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/validation/test_backend_local.py +0 -0
  88. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/validation/test_cerebras_comparison.py +0 -0
  89. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/validation/test_improved_prompt.py +0 -0
  90. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/validation/test_qualitative_robustness.py +0 -0
  91. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/validation/test_qualitative_system.py +0 -0
  92. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/validation/test_truth_seeking_chinese.py +0 -0
  93. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/validation/test_truth_seeking_comprehensive.py +0 -0
  94. {cite_agent-1.2.5 → cite_agent-1.2.7}/tests/validation/test_truth_seeking_real.py +0 -0
  95. {cite_agent-1.2.5 → cite_agent-1.2.7}/tools/security_audit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cite-agent
3
- Version: 1.2.5
3
+ Version: 1.2.7
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
@@ -23,6 +23,7 @@ Requires-Dist: python-dotenv>=1.0.0
23
23
  Requires-Dist: pydantic>=2.5.0
24
24
  Requires-Dist: rich>=13.7.0
25
25
  Requires-Dist: keyring>=24.3.0
26
+ Requires-Dist: ddgs>=1.0.0
26
27
  Dynamic: author
27
28
  Dynamic: author-email
28
29
  Dynamic: classifier
@@ -36,7 +37,7 @@ Dynamic: summary
36
37
 
37
38
  # Cite-Agent: AI Research Assistant
38
39
 
39
- [![Version](https://img.shields.io/badge/version-1.2.0-blue.svg)](https://pypi.org/project/cite-agent/)
40
+ [![Version](https://img.shields.io/badge/version-1.2.6-blue.svg)](https://pypi.org/project/cite-agent/)
40
41
  [![Python](https://img.shields.io/badge/python-3.8+-green.svg)](https://python.org)
41
42
  [![License](https://img.shields.io/badge/license-MIT-orange.svg)](LICENSE)
42
43
 
@@ -80,10 +81,34 @@ Dynamic: summary
80
81
 
81
82
  ### Installation
82
83
 
84
+ **Option 1: pipx (Recommended - handles PATH automatically)**
83
85
  ```bash
84
- pip install cite-agent
86
+ # Install pipx if you don't have it
87
+ pip install --user pipx
88
+ python3 -m pipx ensurepath
89
+
90
+ # Install cite-agent
91
+ pipx install cite-agent
92
+
93
+ # Ready to use (no PATH setup needed)
94
+ cite-agent --version
95
+ ```
96
+
97
+ **Option 2: pip (requires PATH setup)**
98
+ ```bash
99
+ # Install
100
+ pip install --user cite-agent
101
+
102
+ # Add to PATH (one-time setup)
103
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
104
+ source ~/.bashrc
105
+
106
+ # Ready to use
107
+ cite-agent --version
85
108
  ```
86
109
 
110
+ **If cite-agent command not found:** Run `python3 -m cite_agent.cli` instead.
111
+
87
112
  ### Basic Usage
88
113
 
89
114
  ```bash
@@ -1,6 +1,6 @@
1
1
  # Cite-Agent: AI Research Assistant
2
2
 
3
- [![Version](https://img.shields.io/badge/version-1.2.0-blue.svg)](https://pypi.org/project/cite-agent/)
3
+ [![Version](https://img.shields.io/badge/version-1.2.6-blue.svg)](https://pypi.org/project/cite-agent/)
4
4
  [![Python](https://img.shields.io/badge/python-3.8+-green.svg)](https://python.org)
5
5
  [![License](https://img.shields.io/badge/license-MIT-orange.svg)](LICENSE)
6
6
 
@@ -44,10 +44,34 @@
44
44
 
45
45
  ### Installation
46
46
 
47
+ **Option 1: pipx (Recommended - handles PATH automatically)**
47
48
  ```bash
48
- pip install cite-agent
49
+ # Install pipx if you don't have it
50
+ pip install --user pipx
51
+ python3 -m pipx ensurepath
52
+
53
+ # Install cite-agent
54
+ pipx install cite-agent
55
+
56
+ # Ready to use (no PATH setup needed)
57
+ cite-agent --version
58
+ ```
59
+
60
+ **Option 2: pip (requires PATH setup)**
61
+ ```bash
62
+ # Install
63
+ pip install --user cite-agent
64
+
65
+ # Add to PATH (one-time setup)
66
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
67
+ source ~/.bashrc
68
+
69
+ # Ready to use
70
+ cite-agent --version
49
71
  ```
50
72
 
73
+ **If cite-agent command not found:** Run `python3 -m cite_agent.cli` instead.
74
+
51
75
  ### Basic Usage
52
76
 
53
77
  ```bash
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Entry point for python -m cite_agent
4
+ Allows running: python3 -m cite_agent
5
+ """
6
+
7
+ from cite_agent.cli import main
8
+
9
+ if __name__ == "__main__":
10
+ main()
11
+
@@ -0,0 +1 @@
1
+ __version__ = "1.2.7"
@@ -95,10 +95,15 @@ class NocturnalCLI:
95
95
  from pathlib import Path
96
96
  session_file = Path.home() / ".nocturnal_archive" / "session.json"
97
97
  has_env_creds = os.getenv("NOCTURNAL_ACCOUNT_EMAIL") and os.getenv("NOCTURNAL_ACCOUNT_PASSWORD")
98
+ use_local_keys = os.getenv("USE_LOCAL_KEYS", "").lower() == "true"
98
99
 
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]")
100
+ if session_file.exists() or has_env_creds or use_local_keys:
101
+ # Skip interactive setup if session exists, env vars present, or using local keys
102
+ if use_local_keys and not session_file.exists():
103
+ # Only show dev mode if explicitly in dev mode (no session)
104
+ self.console.print("[success]⚙️ Dev mode - using local API keys.[/success]")
105
+ else:
106
+ self.console.print("[success]⚙️ Using saved credentials.[/success]")
102
107
  else:
103
108
  # Need interactive setup
104
109
  if non_interactive:
@@ -604,6 +609,16 @@ class NocturnalCLI:
604
609
 
605
610
  def main():
606
611
  """Main CLI entry point"""
612
+ # Check if ~/.local/bin is in PATH and warn if not
613
+ import sys
614
+ from pathlib import Path
615
+ local_bin = Path.home() / ".local" / "bin"
616
+ if local_bin.exists() and str(local_bin) not in os.environ.get("PATH", ""):
617
+ print("⚠️ NOTE: ~/.local/bin is not in your PATH")
618
+ print(" Add this to ~/.bashrc or ~/.zshrc:")
619
+ print(' export PATH="$HOME/.local/bin:$PATH"')
620
+ print("")
621
+
607
622
  parser = argparse.ArgumentParser(
608
623
  description="Cite Agent - AI Research Assistant with real data",
609
624
  formatter_class=argparse.RawDescriptionHelpFormatter,
@@ -743,7 +758,7 @@ Examples:
743
758
 
744
759
  # Handle version
745
760
  if args.version:
746
- print("Cite Agent v1.2.5")
761
+ print("Cite Agent v1.2.7")
747
762
  print("AI Research Assistant with real data integration")
748
763
  return
749
764
 
@@ -72,6 +72,14 @@ class EnhancedNocturnalAgent:
72
72
  self.daily_limit = 100000
73
73
  self.daily_query_limit = self._resolve_daily_query_limit()
74
74
  self.per_user_query_limit = self.daily_query_limit
75
+
76
+ # Initialize web search for fallback
77
+ self.web_search = None
78
+ try:
79
+ from .web_search import WebSearchIntegration
80
+ self.web_search = WebSearchIntegration()
81
+ except Exception:
82
+ pass # Web search optional
75
83
  self.daily_query_count = 0
76
84
  self.total_cost = 0.0
77
85
  self.cost_per_1k_tokens = 0.0001 # Groq pricing estimate
@@ -477,12 +485,17 @@ class EnhancedNocturnalAgent:
477
485
 
478
486
  try:
479
487
  from dotenv import load_dotenv
480
-
481
- load_dotenv('.env.local')
488
+ from pathlib import Path
489
+
490
+ # ONLY load from user's config directory (never from cwd/project root)
491
+ # Project .env.local is for developers, not end users
492
+ env_local = Path.home() / ".nocturnal_archive" / ".env.local"
493
+ if env_local.exists():
494
+ load_dotenv(env_local, override=False) # Don't override existing env vars
482
495
  except ImportError:
483
- print("⚠️ python-dotenv not installed, using system environment variables")
496
+ pass # python-dotenv not installed
484
497
  except Exception as exc:
485
- print(f"⚠️ Could not load .env.local: {exc}")
498
+ pass # Silently fail - not critical
486
499
  finally:
487
500
  self._env_loaded = True
488
501
 
@@ -856,7 +869,7 @@ class EnhancedNocturnalAgent:
856
869
  serialized = json.dumps(api_results, indent=2)
857
870
  except Exception:
858
871
  serialized = str(api_results)
859
- max_len = 8000 # Keep under 12K token limit (backend + context)
872
+ max_len = 3000 # Aggressive limit to prevent token explosion
860
873
  if len(serialized) > max_len:
861
874
  serialized = serialized[:max_len] + "\n... (truncated for length)"
862
875
 
@@ -896,12 +909,24 @@ class EnhancedNocturnalAgent:
896
909
  "You have access to production data sources and can write/execute code (Python, R, SQL)."
897
910
  )
898
911
  else: # quantitative
899
- intro = (
900
- "You are Cite Agent, a truth-seeking research and finance AI. "
901
- "PRIMARY DIRECTIVE: Accuracy > Agreeableness. Ask clarifying questions when context is missing. "
902
- "You are a fact-checker and analyst, NOT a people-pleaser. "
903
- "You have direct access to production-grade data sources and can write/execute code (Python, R, SQL)."
904
- )
912
+ # Check if we're in dev mode (has local LLM client)
913
+ dev_mode = self.client is not None
914
+
915
+ if dev_mode:
916
+ intro = (
917
+ "You are Cite Agent, a data analysis and research assistant with CODE EXECUTION. "
918
+ "PRIMARY DIRECTIVE: Execute code when needed. You have a persistent shell session. "
919
+ "When user asks for data analysis, calculations, or file operations: WRITE and EXECUTE the code. "
920
+ "Languages available: Python, R, SQL, Bash. "
921
+ "You can read files, run scripts, perform calculations, and show results."
922
+ )
923
+ else:
924
+ intro = (
925
+ "You are Cite Agent, a truth-seeking research and finance AI. "
926
+ "PRIMARY DIRECTIVE: Accuracy > Agreeableness. Ask clarifying questions when context is missing. "
927
+ "You are a fact-checker and analyst, NOT a people-pleaser. "
928
+ "You have access to research (Archive) and financial data (FinSight SEC filings)."
929
+ )
905
930
 
906
931
  sections.append(intro)
907
932
 
@@ -930,16 +955,20 @@ class EnhancedNocturnalAgent:
930
955
 
931
956
  # ENHANCED TRUTH-SEEKING RULES (adapt based on mode)
932
957
  base_rules = [
933
- "🚨 BE PATIENT: Don't rush to tools. Have a conversation to understand intent FIRST.",
934
- "🚨 CLARIFY BEFORE SEARCH: If you see '2008, 2015, 2019' ask 'Are you looking for crisis patterns? Economic events? Papers published in those years?' DON'T just search '2008'.",
935
- "🚨 KNOW YOUR TOOLS' LIMITS: SEC has revenue, not market share. Archive has papers, not market data. If tool can't answer, say 'I don't have that data' or use web search.",
936
- "🚨 TOOL != ANSWER: Don't use tools just because you have them. Revenue ≠ Market Share. Published year ≠ Subject matter.",
958
+ "🚨 BE RESOURCEFUL: You have Archive, FinSight (SEC+Yahoo), and Web Search. USE them to find answers.",
959
+ "🚨 TRY TOOLS FIRST: Before asking user for clarification, try your tools to find the answer.",
960
+ "🚨 WEB SEARCH IS YOUR FRIEND: Market share? Industry size? Current prices? Web search can find it.",
961
+ "🚨 ONLY ask clarification if tools can't help AND query is truly ambiguous.",
962
+ "",
963
+ "💬 AUTONOMOUS FLOW:",
964
+ "1. User asks question → YOU use tools to find data",
965
+ "2. If partial data → YOU web search for missing pieces",
966
+ "3. YOU synthesize → Present complete answer",
967
+ "4. ONLY if impossible → Ask for clarification",
937
968
  "",
938
- "💬 CONVERSATIONAL FLOW:",
939
- "1. User asks vague questionYOU ask clarifying questions",
940
- "2. User provides contextYOU confirm understanding",
941
- "3. YOU make tool calls → Present results",
942
- "NEVER skip step 1 or 2. Be deliberate, not eager.",
969
+ "Examples:",
970
+ " BAD: 'Snowflake market share?''Which market?' (when web search can tell you!)",
971
+ " GOOD: 'Snowflake market share?' [web search] → '18.33% in cloud data warehouses'",
943
972
  "",
944
973
  "🚨 ANTI-APPEASEMENT: If user states something incorrect, CORRECT THEM immediately. Do not agree to be polite.",
945
974
  "🚨 UNCERTAINTY: If you're uncertain, SAY SO explicitly. 'I don't know' is better than a wrong answer.",
@@ -1407,18 +1436,23 @@ class EnhancedNocturnalAgent:
1407
1436
  # DISABLED for beta testing - set USE_LOCAL_KEYS=false to enable backend-only mode
1408
1437
 
1409
1438
  # SECURITY: Production users MUST use backend for monetization
1410
- # Dev mode only available via undocumented env var (not in user docs)
1439
+ # Priority: 1) Session exists backend, 2) USE_LOCAL_KEYS dev mode
1440
+ from pathlib import Path
1441
+ session_file = Path.home() / ".nocturnal_archive" / "session.json"
1442
+ has_session = session_file.exists()
1411
1443
  use_local_keys_env = os.getenv("USE_LOCAL_KEYS", "").lower()
1412
1444
 
1413
- if use_local_keys_env == "true":
1414
- # Dev mode - use local keys
1445
+ if has_session:
1446
+ # Session exists ALWAYS use backend mode (ignore USE_LOCAL_KEYS)
1447
+ use_local_keys = False
1448
+ elif use_local_keys_env == "true":
1449
+ # No session but dev mode requested → use local keys
1415
1450
  use_local_keys = True
1416
1451
  elif use_local_keys_env == "false":
1417
1452
  # Explicit backend mode
1418
1453
  use_local_keys = False
1419
1454
  else:
1420
1455
  # Default: Always use backend (for monetization)
1421
- # Even if session doesn't exist, we'll prompt for login
1422
1456
  use_local_keys = False
1423
1457
 
1424
1458
  if not use_local_keys:
@@ -1986,59 +2020,61 @@ class EnhancedNocturnalAgent:
1986
2020
  return results
1987
2021
 
1988
2022
  def execute_command(self, command: str) -> str:
1989
- """Execute command in persistent shell session and return output"""
2023
+ """Execute command and return output - improved with echo markers"""
1990
2024
  try:
1991
2025
  if self.shell_session is None:
1992
2026
  return "ERROR: Shell session not initialized"
1993
2027
 
1994
- # Send command to persistent shell
1995
- self.shell_session.stdin.write(command + '\n')
1996
- self.shell_session.stdin.flush()
2028
+ # Clean command - remove natural language prefixes
2029
+ command = command.strip()
2030
+ prefixes_to_remove = [
2031
+ 'run this bash:', 'execute this:', 'run command:', 'execute:',
2032
+ 'run this:', 'run:', 'bash:', 'command:', 'this bash:', 'this:',
2033
+ 'r code to', 'R code to', 'python code to', 'in r:', 'in R:',
2034
+ 'in python:', 'in bash:', 'with r:', 'with bash:'
2035
+ ]
2036
+ for prefix in prefixes_to_remove:
2037
+ if command.lower().startswith(prefix.lower()):
2038
+ command = command[len(prefix):].strip()
2039
+ # Try again in case of nested prefixes
2040
+ for prefix2 in prefixes_to_remove:
2041
+ if command.lower().startswith(prefix2.lower()):
2042
+ command = command[len(prefix2):].strip()
2043
+ break
2044
+ break
1997
2045
 
1998
- # Read output with timeout
1999
- try:
2000
- import select
2001
- use_select = True
2002
- except ImportError:
2003
- # Windows doesn't have select module
2004
- use_select = False
2046
+ # Use echo markers to detect when command is done
2047
+ import uuid
2048
+ marker = f"CMD_DONE_{uuid.uuid4().hex[:8]}"
2049
+
2050
+ # Send command with marker
2051
+ full_command = f"{command}; echo '{marker}'\n"
2052
+ self.shell_session.stdin.write(full_command)
2053
+ self.shell_session.stdin.flush()
2005
2054
 
2055
+ # Read until we see the marker
2006
2056
  output_lines = []
2007
2057
  start_time = time.time()
2008
- timeout = 10 # seconds
2058
+ timeout = 30 # Increased for R scripts
2009
2059
 
2010
- if use_select:
2011
- while time.time() - start_time < timeout:
2012
- if select.select([self.shell_session.stdout], [], [], 0.1)[0]:
2013
- line = self.shell_session.stdout.readline()
2014
- if line:
2015
- output_lines.append(line.rstrip())
2016
- else:
2017
- break
2018
- else:
2019
- # No more output available
2060
+ while time.time() - start_time < timeout:
2061
+ try:
2062
+ line = self.shell_session.stdout.readline()
2063
+ if not line:
2020
2064
  break
2021
- else:
2022
- # Fallback for Windows - simpler approach
2023
- import threading
2024
-
2025
- def read_output():
2026
- try:
2027
- while True:
2028
- line = self.shell_session.stdout.readline()
2029
- if line:
2030
- output_lines.append(line.rstrip())
2031
- else:
2032
- break
2033
- except:
2034
- pass
2035
-
2036
- reader_thread = threading.Thread(target=read_output, daemon=True)
2037
- reader_thread.start()
2038
- reader_thread.join(timeout=timeout)
2065
+
2066
+ line = line.rstrip()
2067
+
2068
+ # Check if we hit the marker
2069
+ if marker in line:
2070
+ break
2071
+
2072
+ output_lines.append(line)
2073
+ except Exception:
2074
+ break
2039
2075
 
2040
- output = '\n'.join(output_lines)
2041
- return output if output else "Command executed successfully"
2076
+ output = '\n'.join(output_lines).strip()
2077
+ return output if output else "Command executed (no output)"
2042
2078
 
2043
2079
  except Exception as e:
2044
2080
  return f"ERROR: {e}"
@@ -2515,21 +2551,30 @@ class EnhancedNocturnalAgent:
2515
2551
  if debug_mode:
2516
2552
  print(f"🔍 Request analysis: {request_analysis}")
2517
2553
 
2518
- # Check if query is too vague - skip API calls to save tokens
2554
+ # Check if query is too vague - skip EXPENSIVE API calls to save tokens
2555
+ # But still allow web search (cheap and flexible)
2519
2556
  is_vague = self._is_query_too_vague_for_apis(request.question)
2520
2557
  if debug_mode and is_vague:
2521
- print(f"🔍 Query detected as VAGUE - skipping API calls, asking for clarification")
2558
+ print(f"🔍 Query detected as VAGUE - skipping Archive/FinSight, but may use web search")
2522
2559
 
2523
2560
  # Call appropriate APIs (Archive, FinSight) - BOTH production and dev mode
2524
2561
  api_results = {}
2525
2562
  tools_used = []
2526
2563
 
2527
- # Skip API calls if query is too vague
2564
+ # Skip Archive/FinSight if query is too vague, but still allow web search later
2528
2565
  if not is_vague:
2529
2566
  # Archive API for research
2530
2567
  if "archive" in request_analysis.get("apis", []):
2531
- result = await self.search_academic_papers(request.question, 5)
2568
+ result = await self.search_academic_papers(request.question, 3) # Reduced from 5 to save tokens
2532
2569
  if "error" not in result:
2570
+ # Strip abstracts to save tokens - only keep essential fields
2571
+ if "results" in result:
2572
+ for paper in result["results"]:
2573
+ # Remove heavy fields
2574
+ paper.pop("abstract", None)
2575
+ paper.pop("tldr", None)
2576
+ paper.pop("full_text", None)
2577
+ # Keep only: title, authors, year, doi, url
2533
2578
  api_results["research"] = result
2534
2579
  tools_used.append("archive_api")
2535
2580
 
@@ -2552,10 +2597,25 @@ class EnhancedNocturnalAgent:
2552
2597
  print(f"🔍 Extracted tickers: {tickers}")
2553
2598
 
2554
2599
  if tickers:
2555
- # Call FinSight with proper endpoint format
2600
+ # Detect what metric user is asking for
2601
+ question_lower = request.question.lower()
2602
+ metric = "revenue" # Default
2603
+
2604
+ if any(word in question_lower for word in ['market cap', 'marketcap', 'market value', 'valuation']):
2605
+ metric = "marketCap"
2606
+ elif any(word in question_lower for word in ['stock price', 'share price', 'current price', 'trading at']):
2607
+ metric = "price"
2608
+ elif 'profit' in question_lower and 'gross' not in question_lower:
2609
+ metric = "netIncome"
2610
+ elif 'earnings' in question_lower or 'eps' in question_lower:
2611
+ metric = "eps"
2612
+ elif any(word in question_lower for word in ['cash flow', 'cashflow']):
2613
+ metric = "freeCashFlow"
2614
+
2615
+ # Call FinSight with detected metric
2556
2616
  if debug_mode:
2557
- print(f"🔍 Calling FinSight API: calc/{tickers[0]}/revenue")
2558
- financial_data = await self._call_finsight_api(f"calc/{tickers[0]}/revenue")
2617
+ print(f"🔍 Calling FinSight API: calc/{tickers[0]}/{metric}")
2618
+ financial_data = await self._call_finsight_api(f"calc/{tickers[0]}/{metric}")
2559
2619
  if debug_mode:
2560
2620
  print(f"🔍 FinSight returned: {list(financial_data.keys()) if financial_data else None}")
2561
2621
  if financial_data and "error" not in financial_data:
@@ -2565,6 +2625,35 @@ class EnhancedNocturnalAgent:
2565
2625
  if debug_mode and financial_data:
2566
2626
  print(f"🔍 FinSight error: {financial_data.get('error')}")
2567
2627
 
2628
+ # Web Search fallback - ALWAYS available even for vague queries
2629
+ # Use for: market share, industry data, current events, prices, anything not in APIs
2630
+ if self.web_search:
2631
+ question_lower = request.question.lower()
2632
+ # Only search if query needs data and APIs didn't provide it
2633
+ needs_web_search = (
2634
+ ('market share' in question_lower) or
2635
+ ('market size' in question_lower) or
2636
+ ('industry' in question_lower and not api_results.get('research')) or
2637
+ ('price' in question_lower and ('today' in question_lower or 'current' in question_lower or 'now' in question_lower)) or
2638
+ ('bitcoin' in question_lower or 'btc' in question_lower or 'crypto' in question_lower) or
2639
+ ('exchange rate' in question_lower or 'forex' in question_lower) or
2640
+ (not api_results and 'latest' in question_lower) # Latest news/data
2641
+ )
2642
+
2643
+ if needs_web_search or (not api_results and len(request.question.split()) > 5):
2644
+ try:
2645
+ if debug_mode:
2646
+ print(f"🔍 Using web search for: {request.question[:50]}...")
2647
+ web_results = await self.web_search.search_web(request.question, num_results=3)
2648
+ if web_results and "results" in web_results:
2649
+ api_results["web_search"] = web_results
2650
+ tools_used.append("web_search")
2651
+ if debug_mode:
2652
+ print(f"🔍 Web search returned: {len(web_results.get('results', []))} results")
2653
+ except Exception as e:
2654
+ if debug_mode:
2655
+ print(f"🔍 Web search failed: {e}")
2656
+
2568
2657
  # PRODUCTION MODE: Send to backend LLM with API results
2569
2658
  if self.client is None:
2570
2659
  return await self.call_backend_query(
@@ -179,17 +179,36 @@ class SessionManager:
179
179
 
180
180
  def setup_environment_variables(self):
181
181
  """Set up environment variables for backend mode"""
182
- # PRODUCTION MODE: Force backend, ensure monetization
183
- # NEVER load user's .env files in production
184
-
185
- # Set backend URL if not already set
186
- if "NOCTURNAL_API_URL" not in os.environ:
187
- os.environ["NOCTURNAL_API_URL"] = "https://cite-agent-api-720dfadd602c.herokuapp.com/api"
188
-
189
- # SECURITY: Default to backend mode (USE_LOCAL_KEYS=false)
190
- # This ensures users MUST authenticate and pay
191
- if "USE_LOCAL_KEYS" not in os.environ:
192
- os.environ["USE_LOCAL_KEYS"] = "false"
182
+ # Load .env.local FIRST if it exists (for dev mode)
183
+ try:
184
+ from dotenv import load_dotenv
185
+ from pathlib import Path
186
+ env_local = Path.home() / ".nocturnal_archive" / ".env.local"
187
+ if env_local.exists():
188
+ load_dotenv(env_local)
189
+ import os
190
+ debug = os.getenv("NOCTURNAL_DEBUG", "").lower() == "1"
191
+ if debug:
192
+ print(f"🔍 Loaded .env.local: USE_LOCAL_KEYS={os.getenv('USE_LOCAL_KEYS')}")
193
+ except Exception as e:
194
+ import os
195
+ debug = os.getenv("NOCTURNAL_DEBUG", "").lower() == "1"
196
+ if debug:
197
+ print(f"⚠️ Failed to load .env.local: {e}")
198
+
199
+ # Check if dev mode is enabled
200
+ dev_mode = os.getenv("CITE_AGENT_DEV_MODE", "").lower() == "true"
201
+
202
+ if not dev_mode:
203
+ # PRODUCTION MODE: Force backend, ensure monetization
204
+ # Set backend URL if not already set
205
+ if "NOCTURNAL_API_URL" not in os.environ:
206
+ os.environ["NOCTURNAL_API_URL"] = "https://cite-agent-api-720dfadd602c.herokuapp.com/api"
207
+
208
+ # SECURITY: Default to backend mode (USE_LOCAL_KEYS=false)
209
+ # This ensures users MUST authenticate and pay
210
+ if "USE_LOCAL_KEYS" not in os.environ:
211
+ os.environ["USE_LOCAL_KEYS"] = "false"
193
212
 
194
213
  def get_session_status(self) -> Dict[str, Any]:
195
214
  """Get current session status for debugging"""
@@ -23,23 +23,14 @@ class WebSearchIntegration:
23
23
  self._initialized = False
24
24
 
25
25
  async def _ensure_initialized(self):
26
- """Lazy initialization of SearchEngine"""
26
+ """Lazy initialization with duckduckgo-search"""
27
27
  if not self._initialized:
28
28
  try:
29
- # Import here to avoid circular dependencies
30
- import sys
31
- from pathlib import Path
32
-
33
- # Add src to path if needed
34
- repo_root = Path(__file__).parent.parent.parent
35
- src_path = repo_root / "src"
36
- if str(src_path) not in sys.path:
37
- sys.path.insert(0, str(src_path))
38
-
39
- from services.search_service.search_engine import SearchEngine
40
- self.search_engine = SearchEngine()
29
+ # Use ddgs (duckduckgo search)
30
+ from ddgs import DDGS
31
+ self.search_engine = DDGS()
41
32
  self._initialized = True
42
- logger.info("Web search engine initialized successfully")
33
+ logger.info("Web search engine initialized (DuckDuckGo)")
43
34
  except Exception as e:
44
35
  logger.error(f"Failed to initialize web search: {e}")
45
36
  self.search_engine = None
@@ -74,9 +65,17 @@ class WebSearchIntegration:
74
65
  }
75
66
 
76
67
  try:
77
- results = await self.search_engine.web_search(query, num_results=num_results)
68
+ # Use DDGS.text() for web search
69
+ results_list = []
70
+ for result in self.search_engine.text(query, max_results=num_results):
71
+ results_list.append({
72
+ "title": result.get("title", ""),
73
+ "url": result.get("href", ""),
74
+ "snippet": result.get("body", ""),
75
+ "source": "DuckDuckGo"
76
+ })
78
77
 
79
- if not results:
78
+ if not results_list:
80
79
  return {
81
80
  "success": True,
82
81
  "results": [],
@@ -87,13 +86,13 @@ class WebSearchIntegration:
87
86
  }
88
87
 
89
88
  # Format results conversationally
90
- formatted = self._format_conversational_results(query, results)
89
+ formatted = self._format_conversational_results(query, results_list)
91
90
 
92
91
  return {
93
92
  "success": True,
94
- "results": results,
93
+ "results": results_list,
95
94
  "formatted_response": formatted,
96
- "count": len(results)
95
+ "count": len(results_list)
97
96
  }
98
97
 
99
98
  except Exception as e:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cite-agent
3
- Version: 1.2.5
3
+ Version: 1.2.7
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
@@ -23,6 +23,7 @@ Requires-Dist: python-dotenv>=1.0.0
23
23
  Requires-Dist: pydantic>=2.5.0
24
24
  Requires-Dist: rich>=13.7.0
25
25
  Requires-Dist: keyring>=24.3.0
26
+ Requires-Dist: ddgs>=1.0.0
26
27
  Dynamic: author
27
28
  Dynamic: author-email
28
29
  Dynamic: classifier
@@ -36,7 +37,7 @@ Dynamic: summary
36
37
 
37
38
  # Cite-Agent: AI Research Assistant
38
39
 
39
- [![Version](https://img.shields.io/badge/version-1.2.0-blue.svg)](https://pypi.org/project/cite-agent/)
40
+ [![Version](https://img.shields.io/badge/version-1.2.6-blue.svg)](https://pypi.org/project/cite-agent/)
40
41
  [![Python](https://img.shields.io/badge/python-3.8+-green.svg)](https://python.org)
41
42
  [![License](https://img.shields.io/badge/license-MIT-orange.svg)](LICENSE)
42
43
 
@@ -80,10 +81,34 @@ Dynamic: summary
80
81
 
81
82
  ### Installation
82
83
 
84
+ **Option 1: pipx (Recommended - handles PATH automatically)**
83
85
  ```bash
84
- pip install cite-agent
86
+ # Install pipx if you don't have it
87
+ pip install --user pipx
88
+ python3 -m pipx ensurepath
89
+
90
+ # Install cite-agent
91
+ pipx install cite-agent
92
+
93
+ # Ready to use (no PATH setup needed)
94
+ cite-agent --version
95
+ ```
96
+
97
+ **Option 2: pip (requires PATH setup)**
98
+ ```bash
99
+ # Install
100
+ pip install --user cite-agent
101
+
102
+ # Add to PATH (one-time setup)
103
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
104
+ source ~/.bashrc
105
+
106
+ # Ready to use
107
+ cite-agent --version
85
108
  ```
86
109
 
110
+ **If cite-agent command not found:** Run `python3 -m cite_agent.cli` instead.
111
+
87
112
  ### Basic Usage
88
113
 
89
114
  ```bash
@@ -5,6 +5,7 @@ requirements-dev.txt
5
5
  requirements.txt
6
6
  setup.py
7
7
  cite_agent/__init__.py
8
+ cite_agent/__main__.py
8
9
  cite_agent/__version__.py
9
10
  cite_agent/account_client.py
10
11
  cite_agent/agent_backend_only.py
@@ -6,3 +6,4 @@ python-dotenv>=1.0.0
6
6
  pydantic>=2.5.0
7
7
  rich>=13.7.0
8
8
  keyring>=24.3.0
9
+ ddgs>=1.0.0
@@ -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.2.5",
10
+ version="1.2.7",
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",
@@ -34,6 +34,7 @@ setup(
34
34
  "pydantic>=2.5.0",
35
35
  "rich>=13.7.0",
36
36
  "keyring>=24.3.0",
37
+ "ddgs>=1.0.0", # For web search fallback (DuckDuckGo)
37
38
  ],
38
39
  entry_points={
39
40
  "console_scripts": [
@@ -1 +0,0 @@
1
- __version__ = "1.2.5"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes