cite-agent 1.0.0__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/__distribution__.py +7 -0
- cite_agent/__init__.py +66 -0
- cite_agent/account_client.py +130 -0
- cite_agent/agent_backend_only.py +172 -0
- cite_agent/ascii_plotting.py +296 -0
- cite_agent/auth.py +281 -0
- cite_agent/backend_only_client.py +83 -0
- cite_agent/cli.py +512 -0
- cite_agent/cli_enhanced.py +207 -0
- cite_agent/dashboard.py +339 -0
- cite_agent/enhanced_ai_agent.py +172 -0
- cite_agent/rate_limiter.py +298 -0
- cite_agent/setup_config.py +417 -0
- cite_agent/telemetry.py +85 -0
- cite_agent/ui.py +175 -0
- cite_agent/updater.py +187 -0
- cite_agent/web_search.py +203 -0
- cite_agent-1.0.0.dist-info/METADATA +234 -0
- cite_agent-1.0.0.dist-info/RECORD +23 -0
- cite_agent-1.0.0.dist-info/WHEEL +5 -0
- cite_agent-1.0.0.dist-info/entry_points.txt +3 -0
- cite_agent-1.0.0.dist-info/licenses/LICENSE +21 -0
- cite_agent-1.0.0.dist-info/top_level.txt +1 -0
cite_agent/updater.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Auto-updater for Nocturnal Archive
|
|
4
|
+
Checks for updates and handles installation
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import urllib.request
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional, Dict, Any
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import pkg_resources
|
|
16
|
+
except ImportError:
|
|
17
|
+
pkg_resources = None
|
|
18
|
+
|
|
19
|
+
class NocturnalUpdater:
|
|
20
|
+
"""Handles automatic updates for Nocturnal Archive"""
|
|
21
|
+
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.current_version = self.get_current_version()
|
|
24
|
+
self.package_name = "nocturnal-archive"
|
|
25
|
+
self.pypi_url = f"https://pypi.org/pypi/{self.package_name}/json"
|
|
26
|
+
self.kill_switch_url = "https://api.nocturnal.dev/api/admin/status"
|
|
27
|
+
|
|
28
|
+
def check_kill_switch(self) -> Dict[str, Any]:
|
|
29
|
+
"""Check if kill switch is activated"""
|
|
30
|
+
try:
|
|
31
|
+
with urllib.request.urlopen(self.kill_switch_url, timeout=5) as response:
|
|
32
|
+
data = json.loads(response.read().decode())
|
|
33
|
+
return data
|
|
34
|
+
except Exception:
|
|
35
|
+
# If we can't reach the kill switch API, allow operation
|
|
36
|
+
return {"enabled": True, "message": ""}
|
|
37
|
+
|
|
38
|
+
def get_current_version(self) -> str:
|
|
39
|
+
"""Get current installed version"""
|
|
40
|
+
if pkg_resources:
|
|
41
|
+
try:
|
|
42
|
+
return pkg_resources.get_distribution(self.package_name).version
|
|
43
|
+
except (pkg_resources.DistributionNotFound, Exception):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
# Fallback: try to get version from installed package
|
|
47
|
+
try:
|
|
48
|
+
import nocturnal_archive
|
|
49
|
+
return getattr(nocturnal_archive, '__version__', '1.0.0')
|
|
50
|
+
except ImportError:
|
|
51
|
+
return "1.0.0"
|
|
52
|
+
|
|
53
|
+
def check_for_updates(self) -> Optional[Dict[str, Any]]:
|
|
54
|
+
"""Check if updates are available"""
|
|
55
|
+
try:
|
|
56
|
+
with urllib.request.urlopen(self.pypi_url, timeout=10) as response:
|
|
57
|
+
data = json.loads(response.read().decode())
|
|
58
|
+
|
|
59
|
+
latest_version = data["info"]["version"]
|
|
60
|
+
|
|
61
|
+
if self.is_newer_version(latest_version, self.current_version):
|
|
62
|
+
return {
|
|
63
|
+
"current": self.current_version,
|
|
64
|
+
"latest": latest_version,
|
|
65
|
+
"available": True,
|
|
66
|
+
"release_notes": data["info"].get("description", ""),
|
|
67
|
+
"download_url": f"https://pypi.org/project/{self.package_name}/{latest_version}/"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
"current": self.current_version,
|
|
72
|
+
"latest": latest_version,
|
|
73
|
+
"available": False
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
except urllib.error.HTTPError as e:
|
|
77
|
+
if e.code == 404:
|
|
78
|
+
# Package not published to PyPI yet - this is normal for development
|
|
79
|
+
return {
|
|
80
|
+
"available": False,
|
|
81
|
+
"current": self.current_version,
|
|
82
|
+
"latest": self.current_version,
|
|
83
|
+
"note": "Development version (not published to PyPI)"
|
|
84
|
+
}
|
|
85
|
+
# Silently ignore other HTTP errors
|
|
86
|
+
return None
|
|
87
|
+
except (urllib.error.URLError, Exception):
|
|
88
|
+
# Silently ignore network errors
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def is_newer_version(self, latest: str, current: str) -> bool:
|
|
92
|
+
"""Check if latest version is newer than current"""
|
|
93
|
+
try:
|
|
94
|
+
latest_parts = [int(x) for x in latest.split('.')]
|
|
95
|
+
current_parts = [int(x) for x in current.split('.')]
|
|
96
|
+
|
|
97
|
+
# Pad with zeros if needed
|
|
98
|
+
max_len = max(len(latest_parts), len(current_parts))
|
|
99
|
+
latest_parts.extend([0] * (max_len - len(latest_parts)))
|
|
100
|
+
current_parts.extend([0] * (max_len - len(current_parts)))
|
|
101
|
+
|
|
102
|
+
return latest_parts > current_parts
|
|
103
|
+
except:
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
def update_package(self, force: bool = False) -> bool:
|
|
107
|
+
"""Update the package to latest version"""
|
|
108
|
+
try:
|
|
109
|
+
print("🔄 Updating Nocturnal Archive...")
|
|
110
|
+
|
|
111
|
+
# Check if update is needed
|
|
112
|
+
if not force:
|
|
113
|
+
update_info = self.check_for_updates()
|
|
114
|
+
if not update_info or not update_info["available"]:
|
|
115
|
+
print("✅ No updates available")
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
# Perform update
|
|
119
|
+
cmd = [sys.executable, "-m", "pip", "install", "--upgrade", self.package_name]
|
|
120
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
121
|
+
|
|
122
|
+
if result.returncode == 0:
|
|
123
|
+
new_version = self.get_current_version()
|
|
124
|
+
print(f"✅ Updated to version {new_version}")
|
|
125
|
+
return True
|
|
126
|
+
else:
|
|
127
|
+
print(f"❌ Update failed: {result.stderr}")
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
print(f"❌ Update error: {e}")
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
def show_update_status(self):
|
|
135
|
+
"""Show current update status"""
|
|
136
|
+
print(f"📦 Current version: {self.current_version}")
|
|
137
|
+
|
|
138
|
+
update_info = self.check_for_updates()
|
|
139
|
+
if update_info:
|
|
140
|
+
if update_info["available"]:
|
|
141
|
+
print(f"🆕 Latest version: {update_info['latest']} (available)")
|
|
142
|
+
print(f"📥 Download: {update_info['download_url']}")
|
|
143
|
+
else:
|
|
144
|
+
print(f"✅ Up to date: {update_info['latest']}")
|
|
145
|
+
else:
|
|
146
|
+
print("⚠️ Could not check for updates")
|
|
147
|
+
|
|
148
|
+
def main():
|
|
149
|
+
"""CLI for updater"""
|
|
150
|
+
import argparse
|
|
151
|
+
|
|
152
|
+
parser = argparse.ArgumentParser(description="Nocturnal Archive Updater")
|
|
153
|
+
parser.add_argument("--check", action="store_true", help="Check for updates")
|
|
154
|
+
parser.add_argument("--update", action="store_true", help="Update to latest version")
|
|
155
|
+
parser.add_argument("--force", action="store_true", help="Force update even if up to date")
|
|
156
|
+
parser.add_argument("--status", action="store_true", help="Show update status")
|
|
157
|
+
|
|
158
|
+
args = parser.parse_args()
|
|
159
|
+
|
|
160
|
+
updater = NocturnalUpdater()
|
|
161
|
+
|
|
162
|
+
if args.check:
|
|
163
|
+
update_info = updater.check_for_updates()
|
|
164
|
+
if update_info and update_info["available"]:
|
|
165
|
+
print(f"🆕 Update available: {update_info['current']} → {update_info['latest']}")
|
|
166
|
+
else:
|
|
167
|
+
print("✅ No updates available")
|
|
168
|
+
|
|
169
|
+
elif args.update:
|
|
170
|
+
updater.update_package(force=args.force)
|
|
171
|
+
|
|
172
|
+
elif args.status:
|
|
173
|
+
updater.show_update_status()
|
|
174
|
+
|
|
175
|
+
else:
|
|
176
|
+
# Default: check and offer update
|
|
177
|
+
update_info = updater.check_for_updates()
|
|
178
|
+
if update_info and update_info["available"]:
|
|
179
|
+
print(f"🆕 Update available: {update_info['current']} → {update_info['latest']}")
|
|
180
|
+
response = input("Update now? (y/N): ").strip().lower()
|
|
181
|
+
if response in ['y', 'yes']:
|
|
182
|
+
updater.update_package()
|
|
183
|
+
else:
|
|
184
|
+
print("✅ No updates available")
|
|
185
|
+
|
|
186
|
+
if __name__ == "__main__":
|
|
187
|
+
main()
|
cite_agent/web_search.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Web Search Integration for Enhanced AI Agent
|
|
4
|
+
Simple wrapper around existing SearchEngine for web browsing capability
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Dict, Any, List, Optional
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WebSearchIntegration:
|
|
15
|
+
"""
|
|
16
|
+
Lightweight web search integration using DuckDuckGo
|
|
17
|
+
Formats results conversationally for the agent
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
"""Initialize web search - lazy load SearchEngine to avoid import issues"""
|
|
22
|
+
self.search_engine = None
|
|
23
|
+
self._initialized = False
|
|
24
|
+
|
|
25
|
+
async def _ensure_initialized(self):
|
|
26
|
+
"""Lazy initialization of SearchEngine"""
|
|
27
|
+
if not self._initialized:
|
|
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()
|
|
41
|
+
self._initialized = True
|
|
42
|
+
logger.info("Web search engine initialized successfully")
|
|
43
|
+
except Exception as e:
|
|
44
|
+
logger.error(f"Failed to initialize web search: {e}")
|
|
45
|
+
self.search_engine = None
|
|
46
|
+
self._initialized = False
|
|
47
|
+
|
|
48
|
+
async def search_web(
|
|
49
|
+
self,
|
|
50
|
+
query: str,
|
|
51
|
+
num_results: int = 5
|
|
52
|
+
) -> Dict[str, Any]:
|
|
53
|
+
"""
|
|
54
|
+
Search the web using DuckDuckGo
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
query: Search query
|
|
58
|
+
num_results: Number of results to return (1-10)
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Dict with 'success', 'results', 'formatted_response'
|
|
62
|
+
"""
|
|
63
|
+
await self._ensure_initialized()
|
|
64
|
+
|
|
65
|
+
if not self.search_engine:
|
|
66
|
+
return {
|
|
67
|
+
"success": False,
|
|
68
|
+
"error": "Web search unavailable",
|
|
69
|
+
"formatted_response": (
|
|
70
|
+
"I apologize, but web search is temporarily unavailable. "
|
|
71
|
+
"I can still help with local data analysis, file operations, "
|
|
72
|
+
"and answering questions based on my knowledge."
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
results = await self.search_engine.web_search(query, num_results=num_results)
|
|
78
|
+
|
|
79
|
+
if not results:
|
|
80
|
+
return {
|
|
81
|
+
"success": True,
|
|
82
|
+
"results": [],
|
|
83
|
+
"formatted_response": (
|
|
84
|
+
f"I searched for '{query}' but didn't find relevant results. "
|
|
85
|
+
"Could you rephrase or provide more specific terms?"
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Format results conversationally
|
|
90
|
+
formatted = self._format_conversational_results(query, results)
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
"success": True,
|
|
94
|
+
"results": results,
|
|
95
|
+
"formatted_response": formatted,
|
|
96
|
+
"count": len(results)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logger.error(f"Web search failed: {e}")
|
|
101
|
+
return {
|
|
102
|
+
"success": False,
|
|
103
|
+
"error": str(e),
|
|
104
|
+
"formatted_response": (
|
|
105
|
+
"I encountered an issue while searching the web. "
|
|
106
|
+
"Let me try to help using other available resources."
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
def _format_conversational_results(
|
|
111
|
+
self,
|
|
112
|
+
query: str,
|
|
113
|
+
results: List[Dict[str, Any]]
|
|
114
|
+
) -> str:
|
|
115
|
+
"""
|
|
116
|
+
Format web search results in a natural, conversational way
|
|
117
|
+
Not as bullet-point list, but as synthesized information
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
query: Original search query
|
|
121
|
+
results: List of search results
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Formatted string for natural conversation
|
|
125
|
+
"""
|
|
126
|
+
if not results:
|
|
127
|
+
return f"I couldn't find any results for '{query}'."
|
|
128
|
+
|
|
129
|
+
# Build conversational response
|
|
130
|
+
response_parts = []
|
|
131
|
+
|
|
132
|
+
# Opening
|
|
133
|
+
response_parts.append(
|
|
134
|
+
f"I found {len(results)} relevant results for '{query}':"
|
|
135
|
+
)
|
|
136
|
+
response_parts.append("")
|
|
137
|
+
|
|
138
|
+
# Format each result
|
|
139
|
+
for i, result in enumerate(results, 1):
|
|
140
|
+
title = result.get("title", "Untitled")
|
|
141
|
+
url = result.get("url", "")
|
|
142
|
+
snippet = result.get("snippet", "")
|
|
143
|
+
|
|
144
|
+
# Format naturally
|
|
145
|
+
response_parts.append(f"**{i}. {title}**")
|
|
146
|
+
if snippet:
|
|
147
|
+
response_parts.append(f" {snippet}")
|
|
148
|
+
if url:
|
|
149
|
+
response_parts.append(f" Source: {url}")
|
|
150
|
+
response_parts.append("")
|
|
151
|
+
|
|
152
|
+
return "\n".join(response_parts)
|
|
153
|
+
|
|
154
|
+
async def close(self):
|
|
155
|
+
"""Clean up resources"""
|
|
156
|
+
if self.search_engine:
|
|
157
|
+
try:
|
|
158
|
+
await self.search_engine.close()
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.error(f"Error closing search engine: {e}")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# Convenience function for direct usage
|
|
164
|
+
async def search_web_simple(query: str, num_results: int = 5) -> str:
|
|
165
|
+
"""
|
|
166
|
+
Simple web search that returns formatted string
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
query: Search query
|
|
170
|
+
num_results: Number of results (1-10)
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Formatted search results as string
|
|
174
|
+
"""
|
|
175
|
+
integration = WebSearchIntegration()
|
|
176
|
+
try:
|
|
177
|
+
result = await integration.search_web(query, num_results)
|
|
178
|
+
return result.get("formatted_response", "No results found.")
|
|
179
|
+
finally:
|
|
180
|
+
await integration.close()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# Example usage
|
|
184
|
+
async def example_usage():
|
|
185
|
+
"""Example of how to use web search integration"""
|
|
186
|
+
|
|
187
|
+
integration = WebSearchIntegration()
|
|
188
|
+
|
|
189
|
+
# Search for something
|
|
190
|
+
result = await integration.search_web("Federal Reserve interest rates 2024", num_results=5)
|
|
191
|
+
|
|
192
|
+
if result["success"]:
|
|
193
|
+
print("✅ Search successful!")
|
|
194
|
+
print(f"\nFormatted response:\n{result['formatted_response']}")
|
|
195
|
+
print(f"\nRaw results: {len(result.get('results', []))} items")
|
|
196
|
+
else:
|
|
197
|
+
print(f"❌ Search failed: {result.get('error')}")
|
|
198
|
+
|
|
199
|
+
await integration.close()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
if __name__ == "__main__":
|
|
203
|
+
asyncio.run(example_usage())
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cite-agent
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: AI Research Assistant - Backend-Only Distribution
|
|
5
|
+
Home-page: https://github.com/Spectating101/cite-agent
|
|
6
|
+
Author: Cite-Agent Team
|
|
7
|
+
Author-email: contact@citeagent.dev
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: requests>=2.31.0
|
|
19
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
20
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
21
|
+
Requires-Dist: pydantic>=2.5.0
|
|
22
|
+
Requires-Dist: rich>=13.7.0
|
|
23
|
+
Requires-Dist: keyring>=24.3.0
|
|
24
|
+
Dynamic: author
|
|
25
|
+
Dynamic: author-email
|
|
26
|
+
Dynamic: classifier
|
|
27
|
+
Dynamic: description
|
|
28
|
+
Dynamic: description-content-type
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
Dynamic: requires-dist
|
|
32
|
+
Dynamic: requires-python
|
|
33
|
+
Dynamic: summary
|
|
34
|
+
|
|
35
|
+
# Cite-Agent
|
|
36
|
+
|
|
37
|
+
> A fast, affordable AI research assistant with built-in citation capabilities
|
|
38
|
+
|
|
39
|
+
**Cite-Agent** is a terminal-based AI assistant designed for research and finance, offering 70B model access at half the cost of comparable services.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🎯 What is Cite-Agent?
|
|
44
|
+
|
|
45
|
+
Cite-Agent is an AI-powered research tool that:
|
|
46
|
+
- Answers complex questions using state-of-the-art 70B language models
|
|
47
|
+
- Provides accurate, source-grounded responses (hence "cite")
|
|
48
|
+
- Executes code (Python, R, SQL) for data analysis
|
|
49
|
+
- Operates through a beautiful terminal interface
|
|
50
|
+
- Costs $10/month (vs $20+ for Claude/ChatGPT)
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## ✨ Key Features
|
|
55
|
+
|
|
56
|
+
### 🧠 **Powerful AI**
|
|
57
|
+
- Access to Llama 3.3 70B via Cerebras & Groq
|
|
58
|
+
- Multi-provider fallback for 99.9% uptime
|
|
59
|
+
- 50 queries/day (50,000 tokens)
|
|
60
|
+
- 2-5 second response times
|
|
61
|
+
|
|
62
|
+
### 🎓 **Truth-Seeking**
|
|
63
|
+
- Explicitly designed to correct user errors
|
|
64
|
+
- Admits uncertainty instead of guessing
|
|
65
|
+
- Cites sources and reasoning
|
|
66
|
+
- Anti-appeasement prompts
|
|
67
|
+
|
|
68
|
+
### 💻 **Code Execution**
|
|
69
|
+
- Python, R, and SQL support
|
|
70
|
+
- Data analysis and visualization
|
|
71
|
+
- Financial calculations
|
|
72
|
+
- Research automation
|
|
73
|
+
|
|
74
|
+
### 🔒 **Secure & Private**
|
|
75
|
+
- API keys never leave the backend
|
|
76
|
+
- JWT-based authentication
|
|
77
|
+
- Rate limiting per user
|
|
78
|
+
- HTTPS-only communication
|
|
79
|
+
|
|
80
|
+
### 📊 **Analytics**
|
|
81
|
+
- Track your usage
|
|
82
|
+
- Monitor token consumption
|
|
83
|
+
- View query history
|
|
84
|
+
- Download statistics
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 💰 Pricing
|
|
89
|
+
|
|
90
|
+
| Plan | Price | Queries/Day | Features |
|
|
91
|
+
|------|-------|-------------|----------|
|
|
92
|
+
| **Student** | $10/month (300 NTD) | 50 | Full access |
|
|
93
|
+
| **Public** | $10/month (400 NTD) | 50 | Full access |
|
|
94
|
+
|
|
95
|
+
**Beta**: First 50 users get 3 months free
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 🚀 Quick Start
|
|
100
|
+
|
|
101
|
+
### For Users
|
|
102
|
+
|
|
103
|
+
1. **Download** for your OS:
|
|
104
|
+
- [Windows](https://cite-agent-api.herokuapp.com/api/downloads/windows)
|
|
105
|
+
- [macOS](https://cite-agent-api.herokuapp.com/api/downloads/macos)
|
|
106
|
+
- [Linux](https://cite-agent-api.herokuapp.com/api/downloads/linux)
|
|
107
|
+
|
|
108
|
+
2. **Install** the downloaded package
|
|
109
|
+
|
|
110
|
+
3. **Run** `cite-agent` in your terminal
|
|
111
|
+
|
|
112
|
+
4. **Register** with email + password
|
|
113
|
+
|
|
114
|
+
5. **Start asking questions!**
|
|
115
|
+
|
|
116
|
+
### For Developers
|
|
117
|
+
|
|
118
|
+
See [DEVELOPMENT.md](./docs/DEVELOPMENT.md) for setup instructions.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 🏗️ Architecture
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
User's Machine Backend (Heroku) AI Providers
|
|
126
|
+
┌─────────────┐ ┌──────────────┐ ┌──────────┐
|
|
127
|
+
│ │ │ │ │ │
|
|
128
|
+
│ Terminal │─────────────▶│ FastAPI │─────────────▶│ Cerebras │
|
|
129
|
+
│ UI │ │ API │ │ (70B) │
|
|
130
|
+
│ │◀─────────────│ │◀─────────────│ │
|
|
131
|
+
│ (cite_ │ │ - Auth │ └──────────┘
|
|
132
|
+
│ agent/) │ │ - Rate │
|
|
133
|
+
│ │ │ Limiting │ ┌──────────┐
|
|
134
|
+
└─────────────┘ │ - Analytics │ │ │
|
|
135
|
+
│ - Proxy │─────────────▶│ Groq │
|
|
136
|
+
│ │ │ (70B) │
|
|
137
|
+
└──────────────┘ │ │
|
|
138
|
+
│ └──────────┘
|
|
139
|
+
┌──────────────┐
|
|
140
|
+
│ PostgreSQL │
|
|
141
|
+
│ Database │
|
|
142
|
+
└──────────────┘
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Components
|
|
146
|
+
|
|
147
|
+
**Frontend (cite_agent/)**
|
|
148
|
+
- Terminal UI using `rich` library
|
|
149
|
+
- JWT authentication
|
|
150
|
+
- Query management
|
|
151
|
+
- Local session storage
|
|
152
|
+
|
|
153
|
+
**Backend (cite-agent-api/)**
|
|
154
|
+
- FastAPI REST API
|
|
155
|
+
- User authentication & rate limiting
|
|
156
|
+
- Multi-provider LLM routing
|
|
157
|
+
- Analytics & tracking
|
|
158
|
+
- PostgreSQL database
|
|
159
|
+
|
|
160
|
+
**AI Providers**
|
|
161
|
+
- **Primary**: Cerebras (14,400 RPD × 3 keys = 43,200/day)
|
|
162
|
+
- **Backup**: Groq (1,000 RPD × 3 keys = 3,000/day)
|
|
163
|
+
- **Total capacity**: ~46,000 queries/day
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 📖 Documentation
|
|
168
|
+
|
|
169
|
+
- **[DEVELOPMENT.md](./docs/DEVELOPMENT.md)** - Developer setup guide
|
|
170
|
+
- **[DEPLOYMENT.md](./DEPLOYMENT.md)** - How to deploy (Heroku)
|
|
171
|
+
- **[API_REFERENCE.md](./docs/API_REFERENCE.md)** - API endpoints
|
|
172
|
+
- **[ARCHITECTURE.md](./ARCHITECTURE.md)** - System design details
|
|
173
|
+
- **[ROADMAP.md](./ROADMAP.md)** - Future plans
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 🛠️ Tech Stack
|
|
178
|
+
|
|
179
|
+
**Frontend**
|
|
180
|
+
- Python 3.13+
|
|
181
|
+
- `rich` for terminal UI
|
|
182
|
+
- `httpx` for async HTTP
|
|
183
|
+
- `pydantic` for data validation
|
|
184
|
+
|
|
185
|
+
**Backend**
|
|
186
|
+
- FastAPI
|
|
187
|
+
- PostgreSQL
|
|
188
|
+
- JWT authentication
|
|
189
|
+
- Multi-provider LLM integration
|
|
190
|
+
|
|
191
|
+
**AI Providers**
|
|
192
|
+
- Cerebras Inference API
|
|
193
|
+
- Groq Cloud
|
|
194
|
+
- Cloudflare Workers AI (fallback)
|
|
195
|
+
- OpenRouter (fallback)
|
|
196
|
+
|
|
197
|
+
**Deployment**
|
|
198
|
+
- Heroku (backend + database)
|
|
199
|
+
- GitHub Releases (installers)
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 🤝 Contributing
|
|
204
|
+
|
|
205
|
+
We're not accepting contributions yet (private beta), but stay tuned!
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## 📜 License
|
|
210
|
+
|
|
211
|
+
Proprietary - All rights reserved
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## 📧 Support
|
|
216
|
+
|
|
217
|
+
- **Email**: s1133958@mail.yzu.edu.tw
|
|
218
|
+
- **Issues**: Coming soon
|
|
219
|
+
- **Documentation**: See `/docs` directory
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 🙏 Acknowledgments
|
|
224
|
+
|
|
225
|
+
- **Cerebras** for generous free tier (14,400 RPD)
|
|
226
|
+
- **Groq** for fast 70B inference
|
|
227
|
+
- **GitHub Student Pack** for free hosting credits
|
|
228
|
+
- **FastAPI** for excellent async framework
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
**Built with ❤️ for researchers and analysts**
|
|
233
|
+
|
|
234
|
+
*Cite-Agent - Because accuracy matters more than agreeableness*
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
cite_agent/__distribution__.py,sha256=U7-p-qBMX7WrQD6WWjRC5b-PswXnlrqAox7EYnLogqI,178
|
|
2
|
+
cite_agent/__init__.py,sha256=wAXV2v8nNOmIAd0rh8196ItBl9hHWBVOBl5Re4VB77I,1645
|
|
3
|
+
cite_agent/account_client.py,sha256=_-3QS_gUSRj3689hE1GuEHXgb9JzaXGZ3o6klkuRQ2Y,4876
|
|
4
|
+
cite_agent/agent_backend_only.py,sha256=Rmi3cUCcTMSHRxZu6MK2rZwme5SCfilxqn0BsoIds_U,5375
|
|
5
|
+
cite_agent/ascii_plotting.py,sha256=lk8BaECs6fmjtp4iH12G09-frlRehAN7HLhHt2crers,8570
|
|
6
|
+
cite_agent/auth.py,sha256=CYBNv8r1_wfdhsx-YcWOiXCiKvPBymaMca6w7JV__FQ,9809
|
|
7
|
+
cite_agent/backend_only_client.py,sha256=WqLF8x7aXTro2Q3ehqKMsdCg53s6fNk9Hy86bGxqmmw,2561
|
|
8
|
+
cite_agent/cli.py,sha256=03GMqjKGosrIvHfUlWBQrFEoPZr563EI6rfTX8YDWmc,18526
|
|
9
|
+
cite_agent/cli_enhanced.py,sha256=EAaSw9qtiYRWUXF6_05T19GCXlz9cCSz6n41ASnXIPc,7407
|
|
10
|
+
cite_agent/dashboard.py,sha256=VGV5XQU1PnqvTsxfKMcue3j2ri_nvm9Be6O5aVays_w,10502
|
|
11
|
+
cite_agent/enhanced_ai_agent.py,sha256=Rmi3cUCcTMSHRxZu6MK2rZwme5SCfilxqn0BsoIds_U,5375
|
|
12
|
+
cite_agent/rate_limiter.py,sha256=-0fXx8Tl4zVB4O28n9ojU2weRo-FBF1cJo9Z5jC2LxQ,10908
|
|
13
|
+
cite_agent/setup_config.py,sha256=kNZNr5cZmCXr43rGWNenNJXZ1Kfz7PrdLXpAqxM7WgM,16404
|
|
14
|
+
cite_agent/telemetry.py,sha256=55kXdHvI24ZsEkbFtihcjIfJt2oiSXcEpLzTxQ3KCdQ,2916
|
|
15
|
+
cite_agent/ui.py,sha256=r1OAeY3NSeqhAjJYmEBH9CaennBuibFAz1Mur6YF80E,6134
|
|
16
|
+
cite_agent/updater.py,sha256=kL2GYL1AKoZ9JoTXxFT5_AkvYvObcCrO2sIVyBw9JgU,7057
|
|
17
|
+
cite_agent/web_search.py,sha256=j-BRhT8EBC6BEPgACQPeVwB1SVGKDz4XLM7sowacvSc,6587
|
|
18
|
+
cite_agent-1.0.0.dist-info/licenses/LICENSE,sha256=XJkyO4IymhSUniN1ENY6lLrL2729gn_rbRlFK6_Hi9M,1074
|
|
19
|
+
cite_agent-1.0.0.dist-info/METADATA,sha256=flKpEvukTe-iqaNmp03t32EseZHxed_rf_nKM09_980,6856
|
|
20
|
+
cite_agent-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
+
cite_agent-1.0.0.dist-info/entry_points.txt,sha256=bJ0u28nFIxQKH1PWQ2ak4PV-FAjhoxTC7YADEdDenFw,83
|
|
22
|
+
cite_agent-1.0.0.dist-info/top_level.txt,sha256=NNfD8pxDZzBK8tjDIpCs2BW9Va-OQ5qUFbEx0SgmyIE,11
|
|
23
|
+
cite_agent-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Nocturnal Archive
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cite_agent
|