cite-agent 1.2.5__py3-none-any.whl → 1.2.7__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/__main__.py +11 -0
- cite_agent/__version__.py +1 -1
- cite_agent/cli.py +19 -4
- cite_agent/enhanced_ai_agent.py +162 -73
- cite_agent/session_manager.py +30 -11
- cite_agent/web_search.py +18 -19
- {cite_agent-1.2.5.dist-info → cite_agent-1.2.7.dist-info}/METADATA +28 -3
- {cite_agent-1.2.5.dist-info → cite_agent-1.2.7.dist-info}/RECORD +12 -11
- {cite_agent-1.2.5.dist-info → cite_agent-1.2.7.dist-info}/WHEEL +0 -0
- {cite_agent-1.2.5.dist-info → cite_agent-1.2.7.dist-info}/entry_points.txt +0 -0
- {cite_agent-1.2.5.dist-info → cite_agent-1.2.7.dist-info}/licenses/LICENSE +0 -0
- {cite_agent-1.2.5.dist-info → cite_agent-1.2.7.dist-info}/top_level.txt +0 -0
cite_agent/__main__.py
ADDED
cite_agent/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.2.
|
|
1
|
+
__version__ = "1.2.7"
|
cite_agent/cli.py
CHANGED
|
@@ -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
|
|
101
|
-
|
|
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.
|
|
761
|
+
print("Cite Agent v1.2.7")
|
|
747
762
|
print("AI Research Assistant with real data integration")
|
|
748
763
|
return
|
|
749
764
|
|
cite_agent/enhanced_ai_agent.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
496
|
+
pass # python-dotenv not installed
|
|
484
497
|
except Exception as exc:
|
|
485
|
-
|
|
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 =
|
|
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
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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
|
|
934
|
-
"🚨
|
|
935
|
-
"🚨
|
|
936
|
-
"🚨
|
|
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
|
-
"
|
|
939
|
-
"
|
|
940
|
-
"
|
|
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
|
-
#
|
|
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
|
|
1414
|
-
#
|
|
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
|
|
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
|
-
#
|
|
1995
|
-
|
|
1996
|
-
|
|
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
|
-
#
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
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 =
|
|
2058
|
+
timeout = 30 # Increased for R scripts
|
|
2009
2059
|
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
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
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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]}/
|
|
2558
|
-
financial_data = await self._call_finsight_api(f"calc/{tickers[0]}/
|
|
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(
|
cite_agent/session_manager.py
CHANGED
|
@@ -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
|
-
#
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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"""
|
cite_agent/web_search.py
CHANGED
|
@@ -23,23 +23,14 @@ class WebSearchIntegration:
|
|
|
23
23
|
self._initialized = False
|
|
24
24
|
|
|
25
25
|
async def _ensure_initialized(self):
|
|
26
|
-
"""Lazy initialization
|
|
26
|
+
"""Lazy initialization with duckduckgo-search"""
|
|
27
27
|
if not self._initialized:
|
|
28
28
|
try:
|
|
29
|
-
#
|
|
30
|
-
import
|
|
31
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
89
|
+
formatted = self._format_conversational_results(query, results_list)
|
|
91
90
|
|
|
92
91
|
return {
|
|
93
92
|
"success": True,
|
|
94
|
-
"results":
|
|
93
|
+
"results": results_list,
|
|
95
94
|
"formatted_response": formatted,
|
|
96
|
-
"count": len(
|
|
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.
|
|
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
|
-
[](https://pypi.org/project/cite-agent/)
|
|
40
41
|
[](https://python.org)
|
|
41
42
|
[](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
|
-
|
|
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,27 +1,28 @@
|
|
|
1
1
|
cite_agent/__init__.py,sha256=wAXV2v8nNOmIAd0rh8196ItBl9hHWBVOBl5Re4VB77I,1645
|
|
2
|
-
cite_agent/
|
|
2
|
+
cite_agent/__main__.py,sha256=6x3lltwG-iZHeQbN12rwvdkPDfd2Rmdk71tOOaC89Mw,179
|
|
3
|
+
cite_agent/__version__.py,sha256=49prCLbE3fFzLfxem5rd2dr1iV4_L-bN0N4J7jxU5yA,22
|
|
3
4
|
cite_agent/account_client.py,sha256=yLuzhIJoIZuXHXGbaVMzDxRATQwcy-wiaLnUrDuwUhI,5725
|
|
4
5
|
cite_agent/agent_backend_only.py,sha256=H4DH4hmKhT0T3rQLAb2xnnJVjxl3pOZaljL9r6JndFY,6314
|
|
5
6
|
cite_agent/ascii_plotting.py,sha256=lk8BaECs6fmjtp4iH12G09-frlRehAN7HLhHt2crers,8570
|
|
6
7
|
cite_agent/auth.py,sha256=YtoGXKwcLkZQbop37iYYL9BzRWBRPlt_D9p71VGViS4,9833
|
|
7
8
|
cite_agent/backend_only_client.py,sha256=WqLF8x7aXTro2Q3ehqKMsdCg53s6fNk9Hy86bGxqmmw,2561
|
|
8
|
-
cite_agent/cli.py,sha256=
|
|
9
|
+
cite_agent/cli.py,sha256=MeIuvCuGT75_5LIf7THAE3potnpd5iCATOqHFw5ExUc,32672
|
|
9
10
|
cite_agent/cli_conversational.py,sha256=RAmgRNRyB8gQ8QLvWU-Tt23j2lmA34rQNT5F3_7SOq0,11141
|
|
10
11
|
cite_agent/cli_enhanced.py,sha256=EAaSw9qtiYRWUXF6_05T19GCXlz9cCSz6n41ASnXIPc,7407
|
|
11
12
|
cite_agent/cli_workflow.py,sha256=4oS_jW9D8ylovXbEFdsyLQONt4o0xxR4Xatfcc4tnBs,11641
|
|
12
13
|
cite_agent/dashboard.py,sha256=VGV5XQU1PnqvTsxfKMcue3j2ri_nvm9Be6O5aVays_w,10502
|
|
13
|
-
cite_agent/enhanced_ai_agent.py,sha256=
|
|
14
|
+
cite_agent/enhanced_ai_agent.py,sha256=eqIsrfRPpmvTorGVO3CC5yuWrN-s5f0Je3bDsO7ho4M,155210
|
|
14
15
|
cite_agent/rate_limiter.py,sha256=-0fXx8Tl4zVB4O28n9ojU2weRo-FBF1cJo9Z5jC2LxQ,10908
|
|
15
|
-
cite_agent/session_manager.py,sha256=
|
|
16
|
+
cite_agent/session_manager.py,sha256=FXj4Odn7Y6JI9cAEsftZX75JWqsujpJ42WKLVmOoZIQ,8860
|
|
16
17
|
cite_agent/setup_config.py,sha256=kNZNr5cZmCXr43rGWNenNJXZ1Kfz7PrdLXpAqxM7WgM,16404
|
|
17
18
|
cite_agent/streaming_ui.py,sha256=N6TWOo7GVQ_Ynfw73JCfrdGcLIU-PwbS3GbsHQHegmg,7810
|
|
18
19
|
cite_agent/telemetry.py,sha256=55kXdHvI24ZsEkbFtihcjIfJt2oiSXcEpLzTxQ3KCdQ,2916
|
|
19
20
|
cite_agent/ui.py,sha256=r1OAeY3NSeqhAjJYmEBH9CaennBuibFAz1Mur6YF80E,6134
|
|
20
21
|
cite_agent/updater.py,sha256=28Kgs6cKEB-3d-Eri411hYHv6CgywMHV0718XFw0PqI,8479
|
|
21
|
-
cite_agent/web_search.py,sha256=
|
|
22
|
+
cite_agent/web_search.py,sha256=FZCuNO7MAITiOIbpPbJyt2bzbXPzQla-9amJpnMpW_4,6520
|
|
22
23
|
cite_agent/workflow.py,sha256=a0YC0Mzz4or1C5t2gZcuJBQ0uMOZrooaI8eLu2kkI0k,15086
|
|
23
24
|
cite_agent/workflow_integration.py,sha256=A9ua0DN5pRtuU0cAwrUTGvqt2SXKhEHQbrHx16EGnDM,10910
|
|
24
|
-
cite_agent-1.2.
|
|
25
|
+
cite_agent-1.2.7.dist-info/licenses/LICENSE,sha256=XJkyO4IymhSUniN1ENY6lLrL2729gn_rbRlFK6_Hi9M,1074
|
|
25
26
|
src/__init__.py,sha256=0eEpjRfjRjOTilP66y-AbGNslBsVYr_clE-bZUzsX7s,40
|
|
26
27
|
src/services/__init__.py,sha256=pTGLCH_84mz4nGtYMwQES5w-LzoSulUtx_uuNM6r-LA,4257
|
|
27
28
|
src/services/simple_enhanced_main.py,sha256=IJoOplCqcVUg3GvN_BRyAhpGrLm_WEPy2jmHcNCY6R0,9257
|
|
@@ -48,8 +49,8 @@ src/services/research_service/synthesizer.py,sha256=lCcu37PWhWVNphHKaJJDIC-JQ5OI
|
|
|
48
49
|
src/services/search_service/__init__.py,sha256=UZFXdd7r6wietQ2kESXEyGffdfBbpghquecQde7auF4,137
|
|
49
50
|
src/services/search_service/indexer.py,sha256=u3-uwdAfmahWWsdebDF9i8XIyp7YtUMIHzlmBLBnPPM,7252
|
|
50
51
|
src/services/search_service/search_engine.py,sha256=S9HqQ_mk-8W4d4MUOgBbEGQGV29-eSuceSFvVb4Xk-k,12500
|
|
51
|
-
cite_agent-1.2.
|
|
52
|
-
cite_agent-1.2.
|
|
53
|
-
cite_agent-1.2.
|
|
54
|
-
cite_agent-1.2.
|
|
55
|
-
cite_agent-1.2.
|
|
52
|
+
cite_agent-1.2.7.dist-info/METADATA,sha256=T5hBq4Be52Rztz1kOAu5Zlvgkx9OK9RvRa4qhE5hA20,12231
|
|
53
|
+
cite_agent-1.2.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
54
|
+
cite_agent-1.2.7.dist-info/entry_points.txt,sha256=bJ0u28nFIxQKH1PWQ2ak4PV-FAjhoxTC7YADEdDenFw,83
|
|
55
|
+
cite_agent-1.2.7.dist-info/top_level.txt,sha256=TgOFqJTIy8vDZuOoYA2QgagkqZtfhM5Acvt_IsWzAKo,15
|
|
56
|
+
cite_agent-1.2.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|