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/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()
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ cite-agent = cite_agent.cli:main
3
+ nocturnal = cite_agent.cli:main
@@ -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