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.

Files changed (116) hide show
  1. {cite_agent-1.2.9/cite_agent.egg-info → cite_agent-1.4.1}/PKG-INFO +6 -1
  2. {cite_agent-1.2.9 → cite_agent-1.4.1}/README.md +6 -1
  3. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/__init__.py +13 -13
  4. cite_agent-1.4.1/cite_agent/__version__.py +1 -0
  5. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/cli.py +266 -14
  6. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/cli_conversational.py +113 -3
  7. cite_agent-1.4.1/cite_agent/conversation_archive.py +152 -0
  8. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/enhanced_ai_agent.py +1953 -532
  9. cite_agent-1.4.1/cite_agent/project_detector.py +148 -0
  10. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/streaming_ui.py +13 -9
  11. {cite_agent-1.2.9 → cite_agent-1.4.1/cite_agent.egg-info}/PKG-INFO +6 -1
  12. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent.egg-info/SOURCES.txt +23 -28
  13. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent.egg-info/top_level.txt +0 -1
  14. cite_agent-1.4.1/docs/AGENT_INTELLIGENCE_REPORT.md +159 -0
  15. {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/BETA_LAUNCH_CHECKLIST.md +2 -0
  16. cite_agent-1.4.1/docs/BETA_PITCH_v1.3.9.md +95 -0
  17. cite_agent-1.4.1/docs/BETA_SHOWCASE_GUIDE.md +43 -0
  18. cite_agent-1.4.1/docs/CONVERSATIONAL_DEPTH_REPORT.md +237 -0
  19. cite_agent-1.4.1/docs/DEV_NOTES_2025-10-30.md +8 -0
  20. cite_agent-1.4.1/docs/FINAL_TEST_RESULTS_CEREBRAS.md +416 -0
  21. cite_agent-1.4.1/docs/TEST_RESULTS_2025-11-01.md +161 -0
  22. cite_agent-1.4.1/docs/TEST_RESULTS_SESSION_2025-11-04.md +195 -0
  23. cite_agent-1.4.1/docs/TEST_RUN_2025-11-01.md +259 -0
  24. cite_agent-1.4.1/docs/TEST_RUN_ANALYSIS_CEREBRAS.md +411 -0
  25. cite_agent-1.4.1/docs/TEST_RUN_UPDATE_2025-11-01.md +217 -0
  26. {cite_agent-1.2.9 → cite_agent-1.4.1}/setup.py +1 -1
  27. cite_agent-1.4.1/tests/enhanced/conftest.py +11 -0
  28. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/enhanced/test_account_client.py +6 -5
  29. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/enhanced/test_archive_agent.py +30 -16
  30. cite_agent-1.4.1/tests/enhanced/test_autonomy_harness.py +124 -0
  31. cite_agent-1.4.1/tests/enhanced/test_conversation_archive.py +41 -0
  32. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/enhanced/test_enhanced_agent_runtime.py +15 -64
  33. cite_agent-1.4.1/tests/enhanced/test_financial_planner.py +59 -0
  34. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/enhanced/test_setup_config.py +1 -5
  35. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/integration_test.py +12 -12
  36. cite_agent-1.4.1/tests/session_affirmation.py +146 -0
  37. cite_agent-1.4.1/tests/test_cli_direct.py +62 -0
  38. cite_agent-1.4.1/tests/test_end_to_end.py +165 -0
  39. cite_agent-1.4.1/tests/test_setup_flow.py +117 -0
  40. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/test_truth_seeking_comprehensive.py +1 -2
  41. cite_agent-1.4.1/tests/test_version_1_0_4.py +91 -0
  42. cite_agent-1.2.9/cite_agent/__version__.py +0 -1
  43. cite_agent-1.2.9/src/__init__.py +0 -1
  44. cite_agent-1.2.9/src/services/__init__.py +0 -132
  45. cite_agent-1.2.9/src/services/auth_service/__init__.py +0 -3
  46. cite_agent-1.2.9/src/services/auth_service/auth_manager.py +0 -33
  47. cite_agent-1.2.9/src/services/graph/__init__.py +0 -1
  48. cite_agent-1.2.9/src/services/graph/knowledge_graph.py +0 -194
  49. cite_agent-1.2.9/src/services/llm_service/__init__.py +0 -5
  50. cite_agent-1.2.9/src/services/llm_service/llm_manager.py +0 -495
  51. cite_agent-1.2.9/src/services/paper_service/__init__.py +0 -5
  52. cite_agent-1.2.9/src/services/paper_service/openalex.py +0 -231
  53. cite_agent-1.2.9/src/services/performance_service/__init__.py +0 -1
  54. cite_agent-1.2.9/src/services/performance_service/rust_performance.py +0 -395
  55. cite_agent-1.2.9/src/services/research_service/__init__.py +0 -23
  56. cite_agent-1.2.9/src/services/research_service/chatbot.py +0 -2056
  57. cite_agent-1.2.9/src/services/research_service/citation_manager.py +0 -436
  58. cite_agent-1.2.9/src/services/research_service/context_manager.py +0 -1441
  59. cite_agent-1.2.9/src/services/research_service/conversation_manager.py +0 -597
  60. cite_agent-1.2.9/src/services/research_service/critical_paper_detector.py +0 -577
  61. cite_agent-1.2.9/src/services/research_service/enhanced_research.py +0 -121
  62. cite_agent-1.2.9/src/services/research_service/enhanced_synthesizer.py +0 -375
  63. cite_agent-1.2.9/src/services/research_service/query_generator.py +0 -777
  64. cite_agent-1.2.9/src/services/research_service/synthesizer.py +0 -1273
  65. cite_agent-1.2.9/src/services/search_service/__init__.py +0 -5
  66. cite_agent-1.2.9/src/services/search_service/indexer.py +0 -186
  67. cite_agent-1.2.9/src/services/search_service/search_engine.py +0 -342
  68. cite_agent-1.2.9/src/services/simple_enhanced_main.py +0 -287
  69. cite_agent-1.2.9/tools/security_audit.py +0 -149
  70. {cite_agent-1.2.9 → cite_agent-1.4.1}/LICENSE +0 -0
  71. {cite_agent-1.2.9 → cite_agent-1.4.1}/MANIFEST.in +0 -0
  72. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/__main__.py +0 -0
  73. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/account_client.py +0 -0
  74. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/agent_backend_only.py +0 -0
  75. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/ascii_plotting.py +0 -0
  76. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/auth.py +0 -0
  77. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/backend_only_client.py +0 -0
  78. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/cli_enhanced.py +0 -0
  79. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/cli_workflow.py +0 -0
  80. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/dashboard.py +0 -0
  81. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/rate_limiter.py +0 -0
  82. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/session_manager.py +0 -0
  83. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/setup_config.py +0 -0
  84. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/telemetry.py +0 -0
  85. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/ui.py +0 -0
  86. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/updater.py +0 -0
  87. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/web_search.py +0 -0
  88. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/workflow.py +0 -0
  89. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent/workflow_integration.py +0 -0
  90. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent.egg-info/dependency_links.txt +0 -0
  91. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent.egg-info/entry_points.txt +0 -0
  92. {cite_agent-1.2.9 → cite_agent-1.4.1}/cite_agent.egg-info/requires.txt +0 -0
  93. {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/BETA_RELEASE_CHECKLIST.md +0 -0
  94. {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/ENHANCED_CAPABILITIES.md +0 -0
  95. {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/GROQ_RATE_LIMITS.md +0 -0
  96. {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/INSTALL.md +0 -0
  97. {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/PUBLISHING_PYPI.md +0 -0
  98. {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/SECURE_PACKAGING_GUIDE.md +0 -0
  99. {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/SECURITY_AUDIT.md +0 -0
  100. {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/USER_GETTING_STARTED.md +0 -0
  101. {cite_agent-1.2.9 → cite_agent-1.4.1}/docs/playbooks/BETA_LAUNCH_PLAYBOOK.md +0 -0
  102. {cite_agent-1.2.9 → cite_agent-1.4.1}/requirements.txt +0 -0
  103. {cite_agent-1.2.9 → cite_agent-1.4.1}/setup.cfg +0 -0
  104. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/beta_launch_test_suite.py +0 -0
  105. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/enhanced/test_reasoning_engine.py +0 -0
  106. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/enhanced/test_tool_framework.py +0 -0
  107. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_accuracy_system.py +0 -0
  108. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_agent_live.py +0 -0
  109. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_backend_local.py +0 -0
  110. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_cerebras_comparison.py +0 -0
  111. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_improved_prompt.py +0 -0
  112. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_qualitative_robustness.py +0 -0
  113. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_qualitative_system.py +0 -0
  114. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_truth_seeking_chinese.py +0 -0
  115. {cite_agent-1.2.9 → cite_agent-1.4.1}/tests/validation/test_truth_seeking_real.py +0 -0
  116. /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.2.9
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__ = "0.9.0b1"
11
- __author__ = "Nocturnal Archive Team"
12
- __email__ = "contact@nocturnal.dev"
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 = "nocturnal-archive"
21
+ PACKAGE_NAME = "cite-agent"
22
22
  PACKAGE_VERSION = __version__
23
- PACKAGE_DESCRIPTION = "Beta CLI agent for finance + research workflows"
24
- PACKAGE_URL = "https://github.com/Spectating101/nocturnal-archive"
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
- 🚀 Nocturnal Archive Quick Start
34
- ================================
33
+ 🚀 Cite Agent Quick Start
34
+ =========================
35
35
 
36
36
  1. Install the package and CLI:
37
- pip install nocturnal-archive
37
+ pip install cite-agent
38
38
 
39
- 2. Configure your Groq key:
40
- nocturnal --setup
39
+ 2. Configure your account or local keys:
40
+ cite-agent --setup
41
41
 
42
42
  3. Ask a question:
43
- nocturnal "Compare Apple and Microsoft net income this quarter"
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 nocturnal_archive import EnhancedNocturnalAgent, ChatRequest
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 loading indicator while processing
301
- with Live(Spinner("dots", text="[dim]Thinking...[/dim]"), console=self.console, transient=True):
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 with proper formatting
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
- # Show usage stats occasionally
326
- if hasattr(self.agent, 'daily_token_usage') and self.agent.daily_token_usage > 0:
327
- stats = self.agent.get_usage_stats()
328
- if stats['usage_percentage'] > 10: # Show if >10% used
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
- print("Cite Agent v1.2.9")
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 user_input.lower() for 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 user_input.lower() for 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
  ])