cite-agent 1.2.9__tar.gz → 1.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cite-agent might be problematic. Click here for more details.
- {cite_agent-1.2.9/cite_agent.egg-info → cite_agent-1.4.1}/PKG-INFO +6 -1
- {cite_agent-1.2.9 → cite_agent-1.4.1}/README.md +6 -1
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/__init__.py +13 -13
- cite_agent-1.4.1/cite_agent/__version__.py +1 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/cli.py +266 -14
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/cli_conversational.py +113 -3
- cite_agent-1.4.1/cite_agent/conversation_archive.py +152 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/enhanced_ai_agent.py +1953 -532
- cite_agent-1.4.1/cite_agent/project_detector.py +148 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/streaming_ui.py +13 -9
- {cite_agent-1.2.9 → cite_agent-1.4.1/cite_agent.egg-info}/PKG-INFO +6 -1
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent.egg-info/SOURCES.txt +23 -28
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent.egg-info/top_level.txt +0 -1
- cite_agent-1.4.1/docs/AGENT_INTELLIGENCE_REPORT.md +159 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/BETA_LAUNCH_CHECKLIST.md +2 -0
- cite_agent-1.4.1/docs/BETA_PITCH_v1.3.9.md +95 -0
- cite_agent-1.4.1/docs/BETA_SHOWCASE_GUIDE.md +43 -0
- cite_agent-1.4.1/docs/CONVERSATIONAL_DEPTH_REPORT.md +237 -0
- cite_agent-1.4.1/docs/DEV_NOTES_2025-10-30.md +8 -0
- cite_agent-1.4.1/docs/FINAL_TEST_RESULTS_CEREBRAS.md +416 -0
- cite_agent-1.4.1/docs/TEST_RESULTS_2025-11-01.md +161 -0
- cite_agent-1.4.1/docs/TEST_RESULTS_SESSION_2025-11-04.md +195 -0
- cite_agent-1.4.1/docs/TEST_RUN_2025-11-01.md +259 -0
- cite_agent-1.4.1/docs/TEST_RUN_ANALYSIS_CEREBRAS.md +411 -0
- cite_agent-1.4.1/docs/TEST_RUN_UPDATE_2025-11-01.md +217 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/setup.py +1 -1
- cite_agent-1.4.1/tests/enhanced/conftest.py +11 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/enhanced/test_account_client.py +6 -5
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/enhanced/test_archive_agent.py +30 -16
- cite_agent-1.4.1/tests/enhanced/test_autonomy_harness.py +124 -0
- cite_agent-1.4.1/tests/enhanced/test_conversation_archive.py +41 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/enhanced/test_enhanced_agent_runtime.py +15 -64
- cite_agent-1.4.1/tests/enhanced/test_financial_planner.py +59 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/enhanced/test_setup_config.py +1 -5
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/integration_test.py +12 -12
- cite_agent-1.4.1/tests/session_affirmation.py +146 -0
- cite_agent-1.4.1/tests/test_cli_direct.py +62 -0
- cite_agent-1.4.1/tests/test_end_to_end.py +165 -0
- cite_agent-1.4.1/tests/test_setup_flow.py +117 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/test_truth_seeking_comprehensive.py +1 -2
- cite_agent-1.4.1/tests/test_version_1_0_4.py +91 -0
- cite_agent-1.2.9/cite_agent/__version__.py +0 -1
- cite_agent-1.2.9/src/__init__.py +0 -1
- cite_agent-1.2.9/src/services/__init__.py +0 -132
- cite_agent-1.2.9/src/services/auth_service/__init__.py +0 -3
- cite_agent-1.2.9/src/services/auth_service/auth_manager.py +0 -33
- cite_agent-1.2.9/src/services/graph/__init__.py +0 -1
- cite_agent-1.2.9/src/services/graph/knowledge_graph.py +0 -194
- cite_agent-1.2.9/src/services/llm_service/__init__.py +0 -5
- cite_agent-1.2.9/src/services/llm_service/llm_manager.py +0 -495
- cite_agent-1.2.9/src/services/paper_service/__init__.py +0 -5
- cite_agent-1.2.9/src/services/paper_service/openalex.py +0 -231
- cite_agent-1.2.9/src/services/performance_service/__init__.py +0 -1
- cite_agent-1.2.9/src/services/performance_service/rust_performance.py +0 -395
- cite_agent-1.2.9/src/services/research_service/__init__.py +0 -23
- cite_agent-1.2.9/src/services/research_service/chatbot.py +0 -2056
- cite_agent-1.2.9/src/services/research_service/citation_manager.py +0 -436
- cite_agent-1.2.9/src/services/research_service/context_manager.py +0 -1441
- cite_agent-1.2.9/src/services/research_service/conversation_manager.py +0 -597
- cite_agent-1.2.9/src/services/research_service/critical_paper_detector.py +0 -577
- cite_agent-1.2.9/src/services/research_service/enhanced_research.py +0 -121
- cite_agent-1.2.9/src/services/research_service/enhanced_synthesizer.py +0 -375
- cite_agent-1.2.9/src/services/research_service/query_generator.py +0 -777
- cite_agent-1.2.9/src/services/research_service/synthesizer.py +0 -1273
- cite_agent-1.2.9/src/services/search_service/__init__.py +0 -5
- cite_agent-1.2.9/src/services/search_service/indexer.py +0 -186
- cite_agent-1.2.9/src/services/search_service/search_engine.py +0 -342
- cite_agent-1.2.9/src/services/simple_enhanced_main.py +0 -287
- cite_agent-1.2.9/tools/security_audit.py +0 -149
- {cite_agent-1.2.9 → cite_agent-1.4.1}/LICENSE +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/MANIFEST.in +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/__main__.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/account_client.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/agent_backend_only.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/ascii_plotting.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/auth.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/backend_only_client.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/cli_enhanced.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/cli_workflow.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/dashboard.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/rate_limiter.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/session_manager.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/setup_config.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/telemetry.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/ui.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/updater.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/web_search.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/workflow.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/workflow_integration.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent.egg-info/dependency_links.txt +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent.egg-info/entry_points.txt +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent.egg-info/requires.txt +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/BETA_RELEASE_CHECKLIST.md +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/ENHANCED_CAPABILITIES.md +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/GROQ_RATE_LIMITS.md +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/INSTALL.md +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/PUBLISHING_PYPI.md +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/SECURE_PACKAGING_GUIDE.md +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/SECURITY_AUDIT.md +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/USER_GETTING_STARTED.md +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/playbooks/BETA_LAUNCH_PLAYBOOK.md +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/requirements.txt +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/setup.cfg +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/beta_launch_test_suite.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/enhanced/test_reasoning_engine.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/enhanced/test_tool_framework.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_accuracy_system.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_agent_live.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_backend_local.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_cerebras_comparison.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_improved_prompt.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_qualitative_robustness.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_qualitative_system.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_truth_seeking_chinese.py +0 -0
- {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_truth_seeking_real.py +0 -0
- /cite_agent-1.2.9/tests/validation/test_truth_seeking_comprehensive.py → /cite_agent-1.4.1/tests/validation/test_truth_seeking_validation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cite-agent
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.1
|
|
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
|
|
@@ -194,6 +194,11 @@ cite-agent "我的p值是0.05,這顯著嗎?"
|
|
|
194
194
|
cite-agent "天空是藍色的嗎?"
|
|
195
195
|
```
|
|
196
196
|
|
|
197
|
+
#### Runtime Controls
|
|
198
|
+
|
|
199
|
+
- Responses render immediately—there’s no artificial typing delay.
|
|
200
|
+
- Press `Ctrl+C` while the agent is thinking or streaming to interrupt and ask a different question on the spot.
|
|
201
|
+
|
|
197
202
|
### Python API Reference
|
|
198
203
|
|
|
199
204
|
#### EnhancedNocturnalAgent
|
|
@@ -157,6 +157,11 @@ cite-agent "我的p值是0.05,這顯著嗎?"
|
|
|
157
157
|
cite-agent "天空是藍色的嗎?"
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
+
#### Runtime Controls
|
|
161
|
+
|
|
162
|
+
- Responses render immediately—there’s no artificial typing delay.
|
|
163
|
+
- Press `Ctrl+C` while the agent is thinking or streaming to interrupt and ask a different question on the spot.
|
|
164
|
+
|
|
160
165
|
### Python API Reference
|
|
161
166
|
|
|
162
167
|
#### EnhancedNocturnalAgent
|
|
@@ -427,4 +432,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
427
432
|
|
|
428
433
|
---
|
|
429
434
|
|
|
430
|
-
**Made with ❤️ for the research community**
|
|
435
|
+
**Made with ❤️ for the research community**
|
|
@@ -7,9 +7,9 @@ prior stacks preserved only in Git history, kept out of the runtime footprint.
|
|
|
7
7
|
|
|
8
8
|
from .enhanced_ai_agent import EnhancedNocturnalAgent, ChatRequest, ChatResponse
|
|
9
9
|
|
|
10
|
-
__version__ = "
|
|
11
|
-
__author__ = "
|
|
12
|
-
__email__ = "contact@
|
|
10
|
+
__version__ = "1.4.0"
|
|
11
|
+
__author__ = "Cite Agent Team"
|
|
12
|
+
__email__ = "contact@citeagent.dev"
|
|
13
13
|
|
|
14
14
|
__all__ = [
|
|
15
15
|
"EnhancedNocturnalAgent",
|
|
@@ -18,10 +18,10 @@ __all__ = [
|
|
|
18
18
|
]
|
|
19
19
|
|
|
20
20
|
# Package metadata
|
|
21
|
-
PACKAGE_NAME = "
|
|
21
|
+
PACKAGE_NAME = "cite-agent"
|
|
22
22
|
PACKAGE_VERSION = __version__
|
|
23
|
-
PACKAGE_DESCRIPTION = "
|
|
24
|
-
PACKAGE_URL = "https://github.com/Spectating101/
|
|
23
|
+
PACKAGE_DESCRIPTION = "Research and finance CLI copilot with shell, Archive, and FinSight tools"
|
|
24
|
+
PACKAGE_URL = "https://github.com/Spectating101/cite-agent"
|
|
25
25
|
|
|
26
26
|
def get_version():
|
|
27
27
|
"""Get the package version"""
|
|
@@ -30,22 +30,22 @@ def get_version():
|
|
|
30
30
|
def quick_start():
|
|
31
31
|
"""Print quick start instructions"""
|
|
32
32
|
print("""
|
|
33
|
-
🚀
|
|
34
|
-
|
|
33
|
+
🚀 Cite Agent Quick Start
|
|
34
|
+
=========================
|
|
35
35
|
|
|
36
36
|
1. Install the package and CLI:
|
|
37
|
-
pip install
|
|
37
|
+
pip install cite-agent
|
|
38
38
|
|
|
39
|
-
2. Configure your
|
|
40
|
-
|
|
39
|
+
2. Configure your account or local keys:
|
|
40
|
+
cite-agent --setup
|
|
41
41
|
|
|
42
42
|
3. Ask a question:
|
|
43
|
-
|
|
43
|
+
cite-agent "Compare Apple and Microsoft net income this quarter"
|
|
44
44
|
|
|
45
45
|
4. Prefer embedding in code? Minimal example:
|
|
46
46
|
```python
|
|
47
47
|
import asyncio
|
|
48
|
-
from
|
|
48
|
+
from cite_agent import EnhancedNocturnalAgent, ChatRequest
|
|
49
49
|
|
|
50
50
|
async def main():
|
|
51
51
|
agent = EnhancedNocturnalAgent()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.4.1"
|
|
@@ -6,13 +6,15 @@ Provides a terminal interface similar to cursor-agent
|
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
8
|
import asyncio
|
|
9
|
+
import json
|
|
9
10
|
import os
|
|
10
11
|
import random
|
|
11
12
|
import sys
|
|
12
13
|
import time
|
|
14
|
+
import hashlib
|
|
13
15
|
from datetime import datetime
|
|
14
16
|
from pathlib import Path
|
|
15
|
-
from typing import Optional
|
|
17
|
+
from typing import Optional, Dict
|
|
16
18
|
|
|
17
19
|
from rich import box
|
|
18
20
|
from rich.console import Console
|
|
@@ -28,6 +30,26 @@ from .cli_workflow import WorkflowCLI
|
|
|
28
30
|
from .workflow import WorkflowManager, Paper, parse_paper_from_response
|
|
29
31
|
from .session_manager import SessionManager
|
|
30
32
|
|
|
33
|
+
PRESET_SCENARIOS: Dict[str, Dict[str, str]] = {
|
|
34
|
+
"Research sprint": {
|
|
35
|
+
"prompt": "Run a literature review on retrieval-augmented generation, summarise three key papers and cite sources.",
|
|
36
|
+
"highlight": "Archive API + guardrails"
|
|
37
|
+
},
|
|
38
|
+
"Data audit": {
|
|
39
|
+
"prompt": "Inspect sales_data.csv, perform exploratory stats, and flag any anomalies worth investigating.",
|
|
40
|
+
"highlight": "Shell analytics + guardrails"
|
|
41
|
+
},
|
|
42
|
+
"Financial briefing": {
|
|
43
|
+
"prompt": "Compare NVDA and AMD revenue and margin trends for the last 4 quarters using FinSight.",
|
|
44
|
+
"highlight": "FinSight multi-ticker"
|
|
45
|
+
},
|
|
46
|
+
"Team handoff": {
|
|
47
|
+
"prompt": "Summarise our last session and note any follow-up tasks saved in the archive for project alpha.",
|
|
48
|
+
"highlight": "Archive memory"
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
31
53
|
class NocturnalCLI:
|
|
32
54
|
"""Command Line Interface for Cite Agent"""
|
|
33
55
|
|
|
@@ -53,6 +75,18 @@ class NocturnalCLI:
|
|
|
53
75
|
"Remember the sandbox: prefix shell commands with [bold]![/] to execute safe utilities only.",
|
|
54
76
|
"If you see an auto-update notice, the CLI will restart itself to load the latest build.",
|
|
55
77
|
]
|
|
78
|
+
self._default_artifacts = Path("artifacts_autonomy.json")
|
|
79
|
+
|
|
80
|
+
def _record_session_event(self, success: bool) -> None:
|
|
81
|
+
try:
|
|
82
|
+
manager = TelemetryManager.get()
|
|
83
|
+
email = os.getenv("NOCTURNAL_ACCOUNT_EMAIL", "")
|
|
84
|
+
payload = {"success": bool(success)}
|
|
85
|
+
if email:
|
|
86
|
+
payload["user"] = hashlib.sha256(email.encode("utf-8")).hexdigest()[:16]
|
|
87
|
+
manager.record("session_login", payload)
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
56
90
|
|
|
57
91
|
def handle_user_friendly_session(self):
|
|
58
92
|
"""Handle session management with user-friendly interface"""
|
|
@@ -78,7 +112,9 @@ class NocturnalCLI:
|
|
|
78
112
|
# Handle user-friendly session management (skip prompts in non-interactive mode)
|
|
79
113
|
if not non_interactive:
|
|
80
114
|
if not self.handle_user_friendly_session():
|
|
115
|
+
self._record_session_event(False)
|
|
81
116
|
return False
|
|
117
|
+
self._record_session_event(True)
|
|
82
118
|
|
|
83
119
|
self._show_intro_panel()
|
|
84
120
|
|
|
@@ -189,13 +225,100 @@ class NocturnalCLI:
|
|
|
189
225
|
def _show_ready_panel(self):
|
|
190
226
|
panel = Panel(
|
|
191
227
|
"Systems check complete.\n"
|
|
192
|
-
"Type [bold]help[/] for commands or [bold]tips[/] for power moves
|
|
228
|
+
"Type [bold]help[/] for commands or [bold]tips[/] for power moves.\n"
|
|
229
|
+
"[dim]Press Ctrl+C while the agent is thinking to interrupt and ask something else.[/dim]",
|
|
193
230
|
title="✅ Cite Agent ready!",
|
|
194
231
|
border_style="green",
|
|
195
232
|
padding=(1, 2),
|
|
196
233
|
box=box.ROUNDED,
|
|
197
234
|
)
|
|
198
235
|
self.console.print(panel)
|
|
236
|
+
|
|
237
|
+
def show_presets(self) -> None:
|
|
238
|
+
table = Table(title="🚀 Beta Showcase Presets", box=box.ROUNDED, show_edge=True)
|
|
239
|
+
table.add_column("Scenario", style="bold cyan")
|
|
240
|
+
table.add_column("Prompt", style="white")
|
|
241
|
+
table.add_column("Highlights", style="magenta")
|
|
242
|
+
for name, payload in PRESET_SCENARIOS.items():
|
|
243
|
+
table.add_row(name, payload["prompt"], payload["highlight"])
|
|
244
|
+
self.console.print(table)
|
|
245
|
+
self.console.print("[dim]Tip: run [/dim][bold]nocturnal \"<prompt>\"[/bold][dim] to execute a preset immediately.[/dim]")
|
|
246
|
+
|
|
247
|
+
def show_metrics(self, artifacts: Optional[Path] = None) -> None:
|
|
248
|
+
artifacts_path = artifacts or self._default_artifacts
|
|
249
|
+
if not artifacts_path.exists():
|
|
250
|
+
self.console.print(
|
|
251
|
+
"[warning]No metrics file found.[/warning] Run [bold]python3 scripts/run_beta_showcase.py[/bold] first."
|
|
252
|
+
)
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
payload = json.loads(artifacts_path.read_text())
|
|
257
|
+
except Exception as exc:
|
|
258
|
+
self.console.print(f"[error]Failed to parse {artifacts_path}: {exc}[/error]")
|
|
259
|
+
return
|
|
260
|
+
|
|
261
|
+
metrics = payload.get("_metrics")
|
|
262
|
+
if not metrics:
|
|
263
|
+
self.console.print(
|
|
264
|
+
"[warning]Metrics summary missing. Regenerate the file with [/warning]"
|
|
265
|
+
"[bold]python3 scripts/run_beta_showcase.py[/bold]."
|
|
266
|
+
)
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
table = Table(title="📊 Beta Harness Summary", box=box.ROUNDED)
|
|
270
|
+
table.add_column("Metric", style="bold green")
|
|
271
|
+
table.add_column("Value", style="white")
|
|
272
|
+
table.add_row("Scenarios", str(metrics.get("scenario_count", "-")))
|
|
273
|
+
elapsed = metrics.get("total_elapsed", 0.0)
|
|
274
|
+
table.add_row("Total elapsed", f"{elapsed:.2f}s")
|
|
275
|
+
guard = metrics.get("guardrail_pass_rate", 0.0)
|
|
276
|
+
table.add_row("Guardrail pass rate", f"{guard:.1%}")
|
|
277
|
+
|
|
278
|
+
tool_usage = metrics.get("tool_usage", {})
|
|
279
|
+
if tool_usage:
|
|
280
|
+
usage_lines = [f"{tool}: {count}" for tool, count in tool_usage.items()]
|
|
281
|
+
table.add_row("Tool invocations", "\n".join(usage_lines))
|
|
282
|
+
|
|
283
|
+
self.console.print(table)
|
|
284
|
+
|
|
285
|
+
guardrail_findings = []
|
|
286
|
+
for name, scenario in payload.items():
|
|
287
|
+
if not isinstance(scenario, dict) or name.startswith("_"):
|
|
288
|
+
continue
|
|
289
|
+
quality = scenario.get("quality_checks")
|
|
290
|
+
if not quality:
|
|
291
|
+
continue
|
|
292
|
+
if not all(quality.values()):
|
|
293
|
+
guardrail_findings.append((name, quality))
|
|
294
|
+
|
|
295
|
+
if guardrail_findings:
|
|
296
|
+
warn_table = Table(title="⚠️ Guardrails needing attention", box=box.ROUNDED, style="yellow")
|
|
297
|
+
warn_table.add_column("Scenario", style="bold")
|
|
298
|
+
warn_table.add_column("Checks", style="white")
|
|
299
|
+
for scenario, checks in guardrail_findings:
|
|
300
|
+
failed = [f"{key}={val}" for key, val in checks.items()]
|
|
301
|
+
warn_table.add_row(scenario, ", ".join(failed))
|
|
302
|
+
self.console.print(warn_table)
|
|
303
|
+
else:
|
|
304
|
+
self.console.print("[success]All guardrails passed.[/success]")
|
|
305
|
+
|
|
306
|
+
def show_token_report(self) -> None:
|
|
307
|
+
try:
|
|
308
|
+
from scripts.token_report import build_token_report
|
|
309
|
+
except Exception as exc: # pragma: no cover - import convenience
|
|
310
|
+
self.console.print(f"[error]Failed to import token report tool: {exc}[/error]")
|
|
311
|
+
return
|
|
312
|
+
|
|
313
|
+
root = Path(os.getenv("NOCTURNAL_HOME", str(Path.home() / ".nocturnal_archive")))
|
|
314
|
+
report = build_token_report(root)
|
|
315
|
+
table = Table(title="🪙 Token Usage", box=box.ROUNDED)
|
|
316
|
+
table.add_column("User (hashed)", style="cyan")
|
|
317
|
+
table.add_column("Tokens", style="white", justify="right")
|
|
318
|
+
for user, tokens in report["per_user"].items():
|
|
319
|
+
table.add_row(user, f"{tokens:.0f}")
|
|
320
|
+
self.console.print(table)
|
|
321
|
+
self.console.print(f"[dim]Total tokens: {report['total_tokens']:.0f}[/dim]")
|
|
199
322
|
|
|
200
323
|
def _enforce_latest_build(self):
|
|
201
324
|
"""Ensure the CLI is running the most recent published build."""
|
|
@@ -256,6 +379,19 @@ class NocturnalCLI:
|
|
|
256
379
|
if not await self.initialize():
|
|
257
380
|
return
|
|
258
381
|
|
|
382
|
+
# Detect if user is in a project directory (R, Python, Node, Jupyter, etc.)
|
|
383
|
+
try:
|
|
384
|
+
from .project_detector import ProjectDetector
|
|
385
|
+
detector = ProjectDetector()
|
|
386
|
+
project_info = detector.detect_project()
|
|
387
|
+
|
|
388
|
+
if project_info:
|
|
389
|
+
# Show project banner
|
|
390
|
+
banner = detector.format_project_banner(project_info)
|
|
391
|
+
self.console.print(banner, style="dim")
|
|
392
|
+
except:
|
|
393
|
+
pass # Silently skip if detection fails
|
|
394
|
+
|
|
259
395
|
self.console.print("\n[bold]🤖 Interactive Mode[/] — Type your questions or 'quit' to exit")
|
|
260
396
|
self.console.rule(style="magenta")
|
|
261
397
|
|
|
@@ -296,18 +432,36 @@ class NocturnalCLI:
|
|
|
296
432
|
try:
|
|
297
433
|
from rich.spinner import Spinner
|
|
298
434
|
from rich.live import Live
|
|
299
|
-
|
|
300
|
-
# Show
|
|
301
|
-
|
|
435
|
+
|
|
436
|
+
# Show detailed progress indicator
|
|
437
|
+
spinner = Spinner("dots", text="[dim]Processing query...[/dim]")
|
|
438
|
+
live = Live(spinner, console=self.console, transient=True)
|
|
439
|
+
live.start()
|
|
440
|
+
|
|
441
|
+
try:
|
|
302
442
|
request = ChatRequest(
|
|
303
443
|
question=user_input,
|
|
304
444
|
user_id="cli_user",
|
|
305
445
|
conversation_id=self.session_id
|
|
306
446
|
)
|
|
307
|
-
|
|
447
|
+
|
|
448
|
+
# Update spinner based on query type
|
|
449
|
+
if any(kw in user_input.lower() for kw in ['read', 'show', 'file', 'cat']):
|
|
450
|
+
spinner.update(text="[cyan]📄 Reading file...[/cyan]")
|
|
451
|
+
elif any(kw in user_input.lower() for kw in ['list', 'ls', 'find', 'search']):
|
|
452
|
+
spinner.update(text="[cyan]🔍 Searching files...[/cyan]")
|
|
453
|
+
elif any(kw in user_input.lower() for kw in ['python', 'calculate', 'run', 'execute']):
|
|
454
|
+
spinner.update(text="[cyan]⚙️ Executing code...[/cyan]")
|
|
455
|
+
elif any(kw in user_input.lower() for kw in ['research', 'paper', 'find', 'archive']):
|
|
456
|
+
spinner.update(text="[cyan]🔬 Searching research database...[/cyan]")
|
|
457
|
+
else:
|
|
458
|
+
spinner.update(text="[cyan]🤖 Thinking...[/cyan]")
|
|
459
|
+
|
|
308
460
|
response = await self.agent.process_request(request)
|
|
461
|
+
finally:
|
|
462
|
+
live.stop()
|
|
309
463
|
|
|
310
|
-
# Print response
|
|
464
|
+
# Print response immediately (no artificial typing delay)
|
|
311
465
|
self.console.print("[bold violet]🤖 Agent[/]: ", end="", highlight=False)
|
|
312
466
|
self.console.print(response.response)
|
|
313
467
|
|
|
@@ -322,12 +476,10 @@ class NocturnalCLI:
|
|
|
322
476
|
}
|
|
323
477
|
)
|
|
324
478
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
self.console.print(f"\n📊 Usage: {stats['usage_percentage']:.1f}% of daily limit")
|
|
330
|
-
|
|
479
|
+
except KeyboardInterrupt:
|
|
480
|
+
live.stop()
|
|
481
|
+
self.console.print("\n[dim]⏹️ Interrupted. Ask another question when ready.[/dim]")
|
|
482
|
+
continue
|
|
331
483
|
except Exception as e:
|
|
332
484
|
self.console.print(f"\n[error]❌ Error: {e}[/error]")
|
|
333
485
|
|
|
@@ -712,6 +864,24 @@ Examples:
|
|
|
712
864
|
action='store_true',
|
|
713
865
|
help='Show recent query history'
|
|
714
866
|
)
|
|
867
|
+
|
|
868
|
+
parser.add_argument(
|
|
869
|
+
'--presets',
|
|
870
|
+
action='store_true',
|
|
871
|
+
help='Show curated beta showcase prompts'
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
parser.add_argument(
|
|
875
|
+
'--metrics',
|
|
876
|
+
action='store_true',
|
|
877
|
+
help='Display the latest autonomy harness metrics summary'
|
|
878
|
+
)
|
|
879
|
+
|
|
880
|
+
parser.add_argument(
|
|
881
|
+
'--token-report',
|
|
882
|
+
action='store_true',
|
|
883
|
+
help='Print aggregated token usage from telemetry logs'
|
|
884
|
+
)
|
|
715
885
|
|
|
716
886
|
parser.add_argument(
|
|
717
887
|
'--search-library',
|
|
@@ -747,10 +917,26 @@ Examples:
|
|
|
747
917
|
|
|
748
918
|
# Handle version
|
|
749
919
|
if args.version:
|
|
750
|
-
|
|
920
|
+
from cite_agent.__version__ import __version__
|
|
921
|
+
print(f"Cite Agent v{__version__}")
|
|
751
922
|
print("AI Research Assistant with real data integration")
|
|
752
923
|
return
|
|
753
924
|
|
|
925
|
+
if args.presets:
|
|
926
|
+
cli = NocturnalCLI()
|
|
927
|
+
cli.show_presets()
|
|
928
|
+
return
|
|
929
|
+
|
|
930
|
+
if args.metrics:
|
|
931
|
+
cli = NocturnalCLI()
|
|
932
|
+
cli.show_metrics()
|
|
933
|
+
return
|
|
934
|
+
|
|
935
|
+
if args.token_report:
|
|
936
|
+
cli = NocturnalCLI()
|
|
937
|
+
cli.show_token_report()
|
|
938
|
+
return
|
|
939
|
+
|
|
754
940
|
if args.tips or (args.query and args.query.lower() == "tips" and not args.interactive):
|
|
755
941
|
cli = NocturnalCLI()
|
|
756
942
|
cli.show_tips()
|
|
@@ -820,6 +1006,72 @@ Examples:
|
|
|
820
1006
|
updater.show_update_status()
|
|
821
1007
|
sys.exit(0)
|
|
822
1008
|
|
|
1009
|
+
# Auto-upgrade on startup (silent, non-blocking)
|
|
1010
|
+
def auto_upgrade_if_needed():
|
|
1011
|
+
"""Automatically upgrade to latest version if available"""
|
|
1012
|
+
try:
|
|
1013
|
+
# Only check once per day to avoid API spam
|
|
1014
|
+
from pathlib import Path
|
|
1015
|
+
import time
|
|
1016
|
+
import subprocess
|
|
1017
|
+
|
|
1018
|
+
check_file = Path.home() / ".cite_agent" / ".last_update_check"
|
|
1019
|
+
check_file.parent.mkdir(exist_ok=True)
|
|
1020
|
+
|
|
1021
|
+
# Check if we've checked recently (within 24 hours)
|
|
1022
|
+
if check_file.exists():
|
|
1023
|
+
last_check = float(check_file.read_text().strip())
|
|
1024
|
+
if time.time() - last_check < 86400: # 24 hours
|
|
1025
|
+
return # Skip check
|
|
1026
|
+
|
|
1027
|
+
updater = NocturnalUpdater()
|
|
1028
|
+
update_info = updater.check_for_updates()
|
|
1029
|
+
|
|
1030
|
+
# Save check timestamp
|
|
1031
|
+
check_file.write_text(str(time.time()))
|
|
1032
|
+
|
|
1033
|
+
if update_info and update_info.get("available"):
|
|
1034
|
+
current = update_info["current"]
|
|
1035
|
+
latest = update_info["latest"]
|
|
1036
|
+
|
|
1037
|
+
print(f"\n🔄 Updating Cite Agent: v{current} → v{latest}...")
|
|
1038
|
+
|
|
1039
|
+
# Detect if installed via pipx or pip
|
|
1040
|
+
import shutil
|
|
1041
|
+
if shutil.which("pipx"):
|
|
1042
|
+
# Try pipx upgrade first
|
|
1043
|
+
result = subprocess.run(
|
|
1044
|
+
["pipx", "upgrade", "cite-agent"],
|
|
1045
|
+
capture_output=True,
|
|
1046
|
+
text=True,
|
|
1047
|
+
timeout=60
|
|
1048
|
+
)
|
|
1049
|
+
if result.returncode == 0:
|
|
1050
|
+
print(f"✅ Updated to v{latest} (via pipx)")
|
|
1051
|
+
print("🔄 Restart cite-agent to use the new version\n")
|
|
1052
|
+
return
|
|
1053
|
+
|
|
1054
|
+
# Fall back to pip install --user
|
|
1055
|
+
result = subprocess.run(
|
|
1056
|
+
[sys.executable, "-m", "pip", "install", "--upgrade", "--user", "cite-agent"],
|
|
1057
|
+
capture_output=True,
|
|
1058
|
+
text=True,
|
|
1059
|
+
timeout=60
|
|
1060
|
+
)
|
|
1061
|
+
|
|
1062
|
+
if result.returncode == 0:
|
|
1063
|
+
print(f"✅ Updated to v{latest}")
|
|
1064
|
+
print("🔄 Restart cite-agent to use the new version\n")
|
|
1065
|
+
else:
|
|
1066
|
+
# Silent fail - don't show errors to users
|
|
1067
|
+
pass
|
|
1068
|
+
except:
|
|
1069
|
+
pass # Silently fail, don't block startup
|
|
1070
|
+
|
|
1071
|
+
# Run auto-upgrade in background (doesn't delay startup)
|
|
1072
|
+
import threading
|
|
1073
|
+
threading.Thread(target=auto_upgrade_if_needed, daemon=True).start()
|
|
1074
|
+
|
|
823
1075
|
# Handle query or interactive mode
|
|
824
1076
|
async def run_cli():
|
|
825
1077
|
cli_instance = NocturnalCLI()
|
|
@@ -8,7 +8,7 @@ import asyncio
|
|
|
8
8
|
import os
|
|
9
9
|
import sys
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import Optional
|
|
11
|
+
from typing import List, Optional
|
|
12
12
|
|
|
13
13
|
# Add nocturnal_archive to path
|
|
14
14
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
@@ -93,6 +93,85 @@ Remember:
|
|
|
93
93
|
|
|
94
94
|
# Store this for when we make requests
|
|
95
95
|
self.jarvis_prompt = jarvis_system_prompt
|
|
96
|
+
|
|
97
|
+
async def _build_environment_snapshot(self, limit: int = 8) -> Optional[str]:
|
|
98
|
+
"""Return a short summary of the current workspace."""
|
|
99
|
+
if not self.agent:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
listing = await self.agent._get_workspace_listing(limit=limit) # type: ignore[attr-defined]
|
|
104
|
+
except Exception:
|
|
105
|
+
listing = {"base": self.working_dir, "items": []}
|
|
106
|
+
|
|
107
|
+
base = listing.get("base") or self.working_dir
|
|
108
|
+
items = listing.get("items") or listing.get("entries") or []
|
|
109
|
+
|
|
110
|
+
lines: List[str] = [f"📂 Working directory: {base}"]
|
|
111
|
+
|
|
112
|
+
if items:
|
|
113
|
+
preview_count = min(len(items), 6)
|
|
114
|
+
preview_lines = [
|
|
115
|
+
f" • {item.get('name')} ({item.get('type', 'item')})"
|
|
116
|
+
for item in items[:preview_count]
|
|
117
|
+
]
|
|
118
|
+
if len(items) > preview_count:
|
|
119
|
+
preview_lines.append(f" • … {len(items) - preview_count} more")
|
|
120
|
+
lines.append("Contents snapshot:\n" + "\n".join(preview_lines))
|
|
121
|
+
|
|
122
|
+
if listing.get("error"):
|
|
123
|
+
lines.append(f"⚠️ Workspace note: {listing['error']}")
|
|
124
|
+
|
|
125
|
+
note = listing.get("note")
|
|
126
|
+
if note:
|
|
127
|
+
lines.append(note)
|
|
128
|
+
|
|
129
|
+
return "\n\n".join(lines)
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def _looks_like_grounding_question(text: str) -> bool:
|
|
133
|
+
lowered = text.lower().strip()
|
|
134
|
+
if not lowered:
|
|
135
|
+
return False
|
|
136
|
+
grounding_phrases = [
|
|
137
|
+
"where are we",
|
|
138
|
+
"where am i",
|
|
139
|
+
"what directory",
|
|
140
|
+
"current directory",
|
|
141
|
+
"pwd",
|
|
142
|
+
"show files",
|
|
143
|
+
"list files",
|
|
144
|
+
"where is this",
|
|
145
|
+
]
|
|
146
|
+
return any(phrase in lowered for phrase in grounding_phrases)
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def _is_small_talk_probe(text: str) -> bool:
|
|
150
|
+
lowered = text.lower().strip()
|
|
151
|
+
return lowered in {"test", "hi", "hello", "hey", "ping"}
|
|
152
|
+
|
|
153
|
+
async def _respond_with_grounding(self) -> None:
|
|
154
|
+
snapshot = await self._build_environment_snapshot()
|
|
155
|
+
if not snapshot:
|
|
156
|
+
snapshot = "I can’t access the workspace details right now, but I’m ready to help."
|
|
157
|
+
|
|
158
|
+
async def snapshot_gen():
|
|
159
|
+
async for chunk in simulate_streaming(snapshot, chunk_size=4):
|
|
160
|
+
yield chunk
|
|
161
|
+
|
|
162
|
+
await self.ui.stream_agent_response(snapshot_gen())
|
|
163
|
+
|
|
164
|
+
async def _respond_with_acknowledgement(self) -> None:
|
|
165
|
+
message = (
|
|
166
|
+
"Ready when you are. Try `help` for guidance or ask me to summarise a file like "
|
|
167
|
+
"`summarize README.md`."
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
async def ack_gen():
|
|
171
|
+
async for chunk in simulate_streaming(message, chunk_size=4):
|
|
172
|
+
yield chunk
|
|
173
|
+
|
|
174
|
+
await self.ui.stream_agent_response(ack_gen())
|
|
96
175
|
|
|
97
176
|
async def run(self):
|
|
98
177
|
"""Main conversation loop"""
|
|
@@ -121,6 +200,24 @@ Remember:
|
|
|
121
200
|
yield chunk
|
|
122
201
|
|
|
123
202
|
await self.ui.stream_agent_response(welcome_gen())
|
|
203
|
+
|
|
204
|
+
snapshot = await self._build_environment_snapshot()
|
|
205
|
+
if snapshot:
|
|
206
|
+
async def snapshot_gen():
|
|
207
|
+
async for chunk in simulate_streaming(snapshot, chunk_size=4):
|
|
208
|
+
yield chunk
|
|
209
|
+
await self.ui.stream_agent_response(snapshot_gen())
|
|
210
|
+
|
|
211
|
+
quick_tips = (
|
|
212
|
+
"Quick tips: `help` for options • `read_file README.md` to inspect docs • "
|
|
213
|
+
"`summarize docs/…` or `analyze data.csv` to get started."
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
async def tips_gen():
|
|
217
|
+
async for chunk in simulate_streaming(quick_tips, chunk_size=4):
|
|
218
|
+
yield chunk
|
|
219
|
+
|
|
220
|
+
await self.ui.stream_agent_response(tips_gen())
|
|
124
221
|
|
|
125
222
|
# Main conversation loop
|
|
126
223
|
while self.conversation_active:
|
|
@@ -160,15 +257,28 @@ Remember:
|
|
|
160
257
|
- Use appropriate tools
|
|
161
258
|
- Stream response naturally
|
|
162
259
|
"""
|
|
260
|
+
|
|
261
|
+
stripped = user_input.strip()
|
|
262
|
+
if not stripped:
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
lowered = stripped.lower()
|
|
266
|
+
|
|
267
|
+
if self._is_small_talk_probe(stripped):
|
|
268
|
+
await self._respond_with_acknowledgement()
|
|
269
|
+
return
|
|
270
|
+
if self._looks_like_grounding_question(stripped):
|
|
271
|
+
await self._respond_with_grounding()
|
|
272
|
+
return
|
|
163
273
|
|
|
164
274
|
# Determine if this is a web search request
|
|
165
|
-
is_web_search = any(keyword in
|
|
275
|
+
is_web_search = any(keyword in lowered for keyword in [
|
|
166
276
|
'google', 'search for', 'browse', 'look up', 'find on the web',
|
|
167
277
|
'what does', 'who is', 'recent news'
|
|
168
278
|
])
|
|
169
279
|
|
|
170
280
|
# Determine if this is a data analysis request
|
|
171
|
-
is_data_analysis = any(keyword in
|
|
281
|
+
is_data_analysis = any(keyword in lowered for keyword in [
|
|
172
282
|
'analyze', 'data', 'csv', 'plot', 'graph', 'test', 'regression',
|
|
173
283
|
'correlation', 'statistics', 'mean', 'median', 'distribution'
|
|
174
284
|
])
|