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.

@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Nocturnal Archive CLI - Simple, Clean, Functional
4
+ Like Cursor/Claude - no clutter
5
+ """
6
+
7
+ import argparse
8
+ import asyncio
9
+ import sys
10
+ from typing import Optional
11
+
12
+ from .enhanced_ai_agent import EnhancedNocturnalAgent, ChatRequest
13
+ from .ui import NocturnalUI, console
14
+ from .auth import AuthManager
15
+ from .setup_config import NocturnalConfig
16
+ from .telemetry import TelemetryManager
17
+
18
+ class NocturnalCLI:
19
+ """Simple CLI - no bloat"""
20
+
21
+ def __init__(self):
22
+ self.agent: Optional[EnhancedNocturnalAgent] = None
23
+ self.auth = AuthManager()
24
+ self.session = None
25
+ self.telemetry = None
26
+ self.queries_today = 0
27
+ self.daily_limit = 25
28
+
29
+ async def run(self):
30
+ """Main entry point"""
31
+ try:
32
+ # Quick welcome
33
+ NocturnalUI.show_welcome()
34
+
35
+ # Check auth
36
+ self.session = self.auth.get_session()
37
+
38
+ if not self.session:
39
+ # Login or register
40
+ if not await self._handle_auth():
41
+ return
42
+
43
+ # Initialize agent
44
+ if not await self._init_agent():
45
+ return
46
+
47
+ # Show status
48
+ email = self.session.get("email", "user")
49
+ NocturnalUI.show_status(email, self.queries_today, self.daily_limit)
50
+
51
+ # Start chat loop
52
+ await self._chat_loop()
53
+
54
+ except KeyboardInterrupt:
55
+ console.print("\nšŸ‘‹ Goodbye!")
56
+ except Exception as e:
57
+ NocturnalUI.show_error(f"Something went wrong: {e}")
58
+
59
+ async def _handle_auth(self) -> bool:
60
+ """Handle login or registration"""
61
+ choice = console.input("[1] Login [2] Register [3] Exit\n> ").strip()
62
+
63
+ if choice == "1":
64
+ email, password = NocturnalUI.prompt_login()
65
+ try:
66
+ with NocturnalUI.show_thinking():
67
+ self.session = self.auth.login(email, password)
68
+ NocturnalUI.show_success("Logged in!")
69
+ return True
70
+ except Exception as e:
71
+ NocturnalUI.show_error(f"Login failed: {e}")
72
+ return False
73
+
74
+ elif choice == "2":
75
+ email, password, license_key = NocturnalUI.prompt_register()
76
+ try:
77
+ with NocturnalUI.show_thinking():
78
+ self.session = self.auth.register(email, password, license_key)
79
+ NocturnalUI.show_success("Account created!")
80
+ return True
81
+ except Exception as e:
82
+ NocturnalUI.show_error(f"Registration failed: {e}")
83
+ return False
84
+
85
+ return False
86
+
87
+ async def _init_agent(self) -> bool:
88
+ """Initialize the AI agent"""
89
+ try:
90
+ config = NocturnalConfig()
91
+ self.telemetry = TelemetryManager(config)
92
+ self.agent = EnhancedNocturnalAgent(config, self.telemetry)
93
+ return True
94
+ except Exception as e:
95
+ NocturnalUI.show_error(f"Failed to initialize: {e}")
96
+ return False
97
+
98
+ async def _chat_loop(self):
99
+ """Main chat loop - polished and responsive"""
100
+ while True:
101
+ try:
102
+ # Get query
103
+ query = NocturnalUI.prompt_query()
104
+
105
+ if not query.strip():
106
+ continue
107
+
108
+ # Handle special commands
109
+ if query.lower() in ['exit', 'quit', 'q']:
110
+ console.print("\n[dim]Thanks for using Nocturnal Archive![/dim]\n")
111
+ break
112
+ elif query.lower() == 'clear':
113
+ console.clear()
114
+ email = self.session.get("email", "user")
115
+ NocturnalUI.show_status(email, self.queries_today, self.daily_limit)
116
+ continue
117
+ elif query.lower() == 'logout':
118
+ self.auth.logout()
119
+ NocturnalUI.show_success("Logged out successfully")
120
+ break
121
+ elif query.lower() in ['help', '?']:
122
+ NocturnalUI.show_help()
123
+ continue
124
+
125
+ # Check daily limit
126
+ if self.queries_today >= self.daily_limit:
127
+ NocturnalUI.show_error(
128
+ f"You've reached your daily limit of {self.daily_limit} queries.\n"
129
+ "Your limit resets tomorrow. Thanks for using Nocturnal Archive!"
130
+ )
131
+ continue
132
+
133
+ # Process query
134
+ with NocturnalUI.show_thinking():
135
+ request = ChatRequest(
136
+ question=query,
137
+ user_id=self.session.get("user_id", "unknown")
138
+ )
139
+ response = await self.agent.process_request(request)
140
+
141
+ # Show response with metadata
142
+ metadata = {
143
+ "tools_used": response.tools_used if response.tools_used else None,
144
+ "sources": f"{len(response.tools_used)} sources" if response.tools_used else None
145
+ }
146
+ NocturnalUI.show_response(response.response, metadata)
147
+
148
+ self.queries_today += 1
149
+
150
+ # Update status in session
151
+ email = self.session.get("email", "user")
152
+
153
+ except KeyboardInterrupt:
154
+ raise
155
+ except Exception as e:
156
+ # Graceful error handling
157
+ NocturnalUI.show_error(
158
+ f"Something went wrong: {str(e)}\n"
159
+ "The error has been logged. Please try rephrasing your question."
160
+ )
161
+
162
+ # Log for developer
163
+ self._log_error(query if 'query' in locals() else "unknown", str(e))
164
+
165
+ def _log_error(self, query: str, error: str):
166
+ """Simple error logging - just append to file for dev to check"""
167
+ import datetime
168
+ from pathlib import Path
169
+
170
+ log_dir = Path.home() / ".nocturnal_archive"
171
+ log_dir.mkdir(exist_ok=True)
172
+
173
+ log_file = log_dir / "errors.log"
174
+
175
+ timestamp = datetime.datetime.now().isoformat()
176
+ email = self.session.get("email", "unknown")
177
+
178
+ with open(log_file, "a") as f:
179
+ f.write(f"\n--- {timestamp} ---\n")
180
+ f.write(f"User: {email}\n")
181
+ f.write(f"Query: {query}\n")
182
+ f.write(f"Error: {error}\n")
183
+
184
+ def main():
185
+ """Entry point"""
186
+ parser = argparse.ArgumentParser(description="Nocturnal Archive - Research & Finance Intelligence")
187
+ parser.add_argument("query", nargs="*", help="Single query to run")
188
+ parser.add_argument("--logout", action="store_true", help="Log out")
189
+ parser.add_argument("--version", action="store_true", help="Show version")
190
+
191
+ args = parser.parse_args()
192
+
193
+ if args.logout:
194
+ AuthManager().logout()
195
+ print("Logged out")
196
+ return
197
+
198
+ if args.version:
199
+ print("Nocturnal Archive v1.0.0-beta")
200
+ return
201
+
202
+ # Run CLI
203
+ cli = NocturnalCLI()
204
+ asyncio.run(cli.run())
205
+
206
+ if __name__ == "__main__":
207
+ main()
@@ -0,0 +1,339 @@
1
+ """
2
+ Nocturnal Archive Developer Dashboard
3
+ Real-time monitoring and analytics for beta deployment
4
+ """
5
+
6
+ from flask import Flask, render_template, jsonify, request
7
+ from flask_cors import CORS
8
+ import json
9
+ import os
10
+ from pathlib import Path
11
+ from datetime import datetime, timedelta
12
+ from collections import defaultdict
13
+ from typing import Dict, List
14
+ import sqlite3
15
+
16
+ app = Flask(__name__)
17
+ CORS(app)
18
+
19
+ class DashboardAnalytics:
20
+ """Analytics engine for the dashboard"""
21
+
22
+ def __init__(self, db_path: str = None):
23
+ self.db_path = db_path or str(Path.home() / ".nocturnal_archive" / "analytics.db")
24
+ self._init_database()
25
+
26
+ def _init_database(self):
27
+ """Initialize SQLite database"""
28
+ os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
29
+
30
+ conn = sqlite3.connect(self.db_path)
31
+ cursor = conn.cursor()
32
+
33
+ # Users table
34
+ cursor.execute("""
35
+ CREATE TABLE IF NOT EXISTS users (
36
+ user_id TEXT PRIMARY KEY,
37
+ email TEXT UNIQUE NOT NULL,
38
+ license_key TEXT,
39
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
40
+ last_active TIMESTAMP,
41
+ total_queries INTEGER DEFAULT 0,
42
+ total_tokens INTEGER DEFAULT 0,
43
+ status TEXT DEFAULT 'active'
44
+ )
45
+ """)
46
+
47
+ # Queries table
48
+ cursor.execute("""
49
+ CREATE TABLE IF NOT EXISTS queries (
50
+ query_id INTEGER PRIMARY KEY AUTOINCREMENT,
51
+ user_id TEXT NOT NULL,
52
+ query_text TEXT,
53
+ tools_used TEXT,
54
+ tokens_used INTEGER,
55
+ response_time REAL,
56
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
57
+ FOREIGN KEY (user_id) REFERENCES users(user_id)
58
+ )
59
+ """)
60
+
61
+ # Usage stats table (daily aggregates)
62
+ cursor.execute("""
63
+ CREATE TABLE IF NOT EXISTS daily_stats (
64
+ date DATE PRIMARY KEY,
65
+ total_users INTEGER,
66
+ active_users INTEGER,
67
+ total_queries INTEGER,
68
+ total_tokens INTEGER,
69
+ avg_response_time REAL
70
+ )
71
+ """)
72
+
73
+ # Errors table
74
+ cursor.execute("""
75
+ CREATE TABLE IF NOT EXISTS errors (
76
+ error_id INTEGER PRIMARY KEY AUTOINCREMENT,
77
+ user_id TEXT,
78
+ error_type TEXT,
79
+ error_message TEXT,
80
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
81
+ )
82
+ """)
83
+
84
+ conn.commit()
85
+ conn.close()
86
+
87
+ def record_query(self, user_id: str, query: str, tools: List[str],
88
+ tokens: int, response_time: float):
89
+ """Record a query"""
90
+ conn = sqlite3.connect(self.db_path)
91
+ cursor = conn.cursor()
92
+
93
+ # Ensure user exists (create if not)
94
+ cursor.execute("""
95
+ INSERT OR IGNORE INTO users (user_id, email, total_queries, total_tokens)
96
+ VALUES (?, ?, 0, 0)
97
+ """, (user_id, f"{user_id}@unknown.dev"))
98
+
99
+ cursor.execute("""
100
+ INSERT INTO queries (user_id, query_text, tools_used, tokens_used, response_time)
101
+ VALUES (?, ?, ?, ?, ?)
102
+ """, (user_id, query, json.dumps(tools), tokens, response_time))
103
+
104
+ # Update user stats
105
+ cursor.execute("""
106
+ UPDATE users
107
+ SET last_active = CURRENT_TIMESTAMP,
108
+ total_queries = total_queries + 1,
109
+ total_tokens = total_tokens + ?
110
+ WHERE user_id = ?
111
+ """, (tokens, user_id))
112
+
113
+ conn.commit()
114
+ conn.close()
115
+
116
+ def get_overview_stats(self) -> Dict:
117
+ """Get overview statistics"""
118
+ conn = sqlite3.connect(self.db_path)
119
+ cursor = conn.cursor()
120
+
121
+ # Total users
122
+ cursor.execute("SELECT COUNT(*) FROM users")
123
+ total_users = cursor.fetchone()[0]
124
+
125
+ # Active users (last 24h)
126
+ cursor.execute("""
127
+ SELECT COUNT(*) FROM users
128
+ WHERE last_active > datetime('now', '-1 day')
129
+ """)
130
+ active_users = cursor.fetchone()[0]
131
+
132
+ # Today's queries
133
+ cursor.execute("""
134
+ SELECT COUNT(*), SUM(tokens_used), AVG(response_time)
135
+ FROM queries
136
+ WHERE DATE(timestamp) = DATE('now')
137
+ """)
138
+ today_queries, today_tokens, avg_response = cursor.fetchone()
139
+
140
+ # Total queries
141
+ cursor.execute("SELECT COUNT(*), SUM(tokens_used) FROM queries")
142
+ total_queries, total_tokens = cursor.fetchone()
143
+
144
+ conn.close()
145
+
146
+ return {
147
+ 'total_users': total_users,
148
+ 'active_users_24h': active_users,
149
+ 'today_queries': today_queries or 0,
150
+ 'today_tokens': today_tokens or 0,
151
+ 'total_queries': total_queries or 0,
152
+ 'total_tokens': total_tokens or 0,
153
+ 'avg_response_time': round(avg_response or 0, 2)
154
+ }
155
+
156
+ def get_user_list(self) -> List[Dict]:
157
+ """Get list of all users with stats"""
158
+ conn = sqlite3.connect(self.db_path)
159
+ conn.row_factory = sqlite3.Row
160
+ cursor = conn.cursor()
161
+
162
+ cursor.execute("""
163
+ SELECT user_id, email, created_at, last_active,
164
+ total_queries, total_tokens, status
165
+ FROM users
166
+ ORDER BY last_active DESC
167
+ """)
168
+
169
+ users = [dict(row) for row in cursor.fetchall()]
170
+ conn.close()
171
+
172
+ return users
173
+
174
+ def get_query_history(self, limit: int = 100) -> List[Dict]:
175
+ """Get recent query history"""
176
+ conn = sqlite3.connect(self.db_path)
177
+ conn.row_factory = sqlite3.Row
178
+ cursor = conn.cursor()
179
+
180
+ cursor.execute("""
181
+ SELECT q.*, u.email
182
+ FROM queries q
183
+ JOIN users u ON q.user_id = u.user_id
184
+ ORDER BY q.timestamp DESC
185
+ LIMIT ?
186
+ """, (limit,))
187
+
188
+ queries = [dict(row) for row in cursor.fetchall()]
189
+ conn.close()
190
+
191
+ return queries
192
+
193
+ def get_usage_trends(self, days: int = 7) -> Dict:
194
+ """Get usage trends over time"""
195
+ conn = sqlite3.connect(self.db_path)
196
+ cursor = conn.cursor()
197
+
198
+ cursor.execute("""
199
+ SELECT DATE(timestamp) as date,
200
+ COUNT(*) as queries,
201
+ SUM(tokens_used) as tokens
202
+ FROM queries
203
+ WHERE timestamp > datetime('now', '-' || ? || ' days')
204
+ GROUP BY DATE(timestamp)
205
+ ORDER BY date
206
+ """, (days,))
207
+
208
+ trends = {
209
+ 'dates': [],
210
+ 'queries': [],
211
+ 'tokens': []
212
+ }
213
+
214
+ for row in cursor.fetchall():
215
+ trends['dates'].append(row[0])
216
+ trends['queries'].append(row[1])
217
+ trends['tokens'].append(row[2])
218
+
219
+ conn.close()
220
+ return trends
221
+
222
+ def kill_switch(self, reason: str = "Emergency shutdown"):
223
+ """Activate kill switch - disable all users"""
224
+ conn = sqlite3.connect(self.db_path)
225
+ cursor = conn.cursor()
226
+
227
+ cursor.execute("""
228
+ UPDATE users SET status = 'disabled'
229
+ WHERE status = 'active'
230
+ """)
231
+
232
+ # Log the kill switch activation
233
+ cursor.execute("""
234
+ INSERT INTO errors (user_id, error_type, error_message)
235
+ VALUES ('SYSTEM', 'KILL_SWITCH', ?)
236
+ """, (reason,))
237
+
238
+ conn.commit()
239
+ affected = cursor.rowcount
240
+ conn.close()
241
+
242
+ return affected
243
+
244
+ def reactivate_users(self):
245
+ """Reactivate all users"""
246
+ conn = sqlite3.connect(self.db_path)
247
+ cursor = conn.cursor()
248
+
249
+ cursor.execute("""
250
+ UPDATE users SET status = 'active'
251
+ WHERE status = 'disabled'
252
+ """)
253
+
254
+ conn.commit()
255
+ affected = cursor.rowcount
256
+ conn.close()
257
+
258
+ return affected
259
+
260
+
261
+ # Initialize analytics
262
+ analytics = DashboardAnalytics()
263
+
264
+ # Routes
265
+ @app.route('/')
266
+ def index():
267
+ """Dashboard home page"""
268
+ return render_template('dashboard.html')
269
+
270
+ @app.route('/api/overview')
271
+ def api_overview():
272
+ """Get overview statistics"""
273
+ return jsonify(analytics.get_overview_stats())
274
+
275
+ @app.route('/api/users')
276
+ def api_users():
277
+ """Get user list"""
278
+ return jsonify(analytics.get_user_list())
279
+
280
+ @app.route('/api/queries')
281
+ def api_queries():
282
+ """Get query history"""
283
+ limit = request.args.get('limit', 100, type=int)
284
+ return jsonify(analytics.get_query_history(limit))
285
+
286
+ @app.route('/api/trends')
287
+ def api_trends():
288
+ """Get usage trends"""
289
+ days = request.args.get('days', 7, type=int)
290
+ return jsonify(analytics.get_usage_trends(days))
291
+
292
+ @app.route('/api/kill-switch', methods=['POST'])
293
+ def api_kill_switch():
294
+ """Activate kill switch"""
295
+ data = request.get_json()
296
+ reason = data.get('reason', 'Emergency shutdown')
297
+
298
+ # Verify admin password
299
+ admin_password = data.get('admin_password')
300
+ if admin_password != os.getenv('NOCTURNAL_ADMIN_PASSWORD', 'admin123'):
301
+ return jsonify({'error': 'Unauthorized'}), 403
302
+
303
+ affected = analytics.kill_switch(reason)
304
+ return jsonify({
305
+ 'success': True,
306
+ 'affected_users': affected,
307
+ 'message': f'Kill switch activated. {affected} users disabled.'
308
+ })
309
+
310
+ @app.route('/api/reactivate', methods=['POST'])
311
+ def api_reactivate():
312
+ """Reactivate all users"""
313
+ data = request.get_json()
314
+
315
+ # Verify admin password
316
+ admin_password = data.get('admin_password')
317
+ if admin_password != os.getenv('NOCTURNAL_ADMIN_PASSWORD', 'admin123'):
318
+ return jsonify({'error': 'Unauthorized'}), 403
319
+
320
+ affected = analytics.reactivate_users()
321
+ return jsonify({
322
+ 'success': True,
323
+ 'affected_users': affected,
324
+ 'message': f'{affected} users reactivated.'
325
+ })
326
+
327
+
328
+ def run_dashboard(host='0.0.0.0', port=5000, debug=False):
329
+ """Run the dashboard server"""
330
+ print(f"šŸš€ Nocturnal Archive Developer Dashboard")
331
+ print(f"šŸ“Š Dashboard: http://localhost:{port}")
332
+ print(f"šŸ”’ Admin password: {os.getenv('NOCTURNAL_ADMIN_PASSWORD', 'admin123')}")
333
+ print()
334
+
335
+ app.run(host=host, port=port, debug=debug)
336
+
337
+
338
+ if __name__ == '__main__':
339
+ run_dashboard(debug=True)