Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
257 changes: 184 additions & 73 deletions anton/chat.py

Large diffs are not rendered by default.

161 changes: 161 additions & 0 deletions anton/commands/bug_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
"""Bug report command handler."""

from __future__ import annotations

import webbrowser
from pathlib import Path
from typing import TYPE_CHECKING

from rich.console import Console

from anton.diagnostics import collect_diagnostics, save_diagnostics_file
from anton.utils.prompt import prompt_or_cancel

from anton.publisher import publish_bug_report

if TYPE_CHECKING:
from anton.chat_session import ChatSession
from anton.config.settings import AntonSettings
from anton.memory.cortex import Cortex
from anton.workspace import Workspace


async def handle_report_bug(
console: Console,
settings: AntonSettings,
workspace: Workspace | None,
session: ChatSession | None,
cortex: Cortex | None,
) -> None:
"""Handle /report-bug command - collect diagnostics and send to bug report endpoint."""
console.print()
console.print("[anton.cyan]Bug Report[/]")
console.print()

# Privacy consent prompt
console.print("[anton.warning]⚠️ Important Privacy Notice[/]")
console.print()
console.print(" This bug report will include:")
console.print(" • Your conversation history from this session")
console.print(" • System information and Anton configuration")
console.print(" • Connected datasource names (no credentials)")
console.print(" • Recent logs and memory state")
console.print()
console.print(
" [bold]Our dev team will be able to see all of this information.[/]"
)
console.print()

consent = await prompt_or_cancel(
" Do you agree to share this information?",
choices=["y", "n"],
choices_display="y/n",
default="n",
)

if consent is None or consent.lower() != "y":
console.print()
console.print(" [anton.muted]Bug report cancelled.[/]")
console.print()
return

console.print()

# Optional bug description
add_description = await prompt_or_cancel(
" Would you like to add a description of the bug?",
choices=["y", "n"],
choices_display="y/n",
default="y",
)

bug_description = None
if add_description and add_description.lower() == "y":
console.print()
console.print(
" [anton.muted]Please describe the bug (press Enter when done):[/]"
)
bug_description = await prompt_or_cancel(" ")
if bug_description is None:
bug_description = ""

console.print()

# Collect diagnostics
from rich.live import Live
from rich.spinner import Spinner

with Live(
Spinner(
"dots", text=" Collecting diagnostic information...", style="anton.cyan"
),
console=console,
transient=True,
):
try:
diagnostics = collect_diagnostics(settings, session, workspace, cortex)

# Add bug description if provided
if bug_description:
diagnostics["user_description"] = bug_description

# Save to file
output_dir = Path(settings.workspace_path) / ".anton" / "output"
diagnostics_file = save_diagnostics_file(diagnostics, output_dir)

except Exception as e:
console.print(f" [anton.error]Failed to collect diagnostics: {e}[/]")
console.print()
return

# Ensure Minds API key is available
if not settings.minds_api_key:
console.print(
" [anton.muted]To submit bug reports you need a free Minds account.[/]"
)
console.print()
has_key = await prompt_or_cancel(
" Do you have an mdb.ai API key?",
choices=["y", "n"],
choices_display="y/n",
default="y",
)
if has_key is None:
console.print()
return
if has_key.lower() == "n":
webbrowser.open("https://mdb.ai/")
console.print()

api_key = await prompt_or_cancel(" API key", password=True)
if api_key is None or not api_key.strip():
console.print()
return
api_key = api_key.strip()
settings.minds_api_key = api_key
if workspace:
workspace.set_secret("ANTON_MINDS_API_KEY", api_key)
console.print()

# Submit bug report
with Live(
Spinner("dots", text=" Submitting bug report...", style="anton.cyan"),
console=console,
transient=True,
):
try:
publish_bug_report(
diagnostics_file,
api_key=settings.minds_api_key,
bug_report_url=settings.bug_report_url or settings.publish_url,
ssl_verify=settings.minds_ssl_verify,
)

except Exception as e:
console.print(f" [anton.error]Failed to submit bug report: {e}[/]")
console.print()
return

console.print(" [anton.success]Bug report submitted successfully![/]")
console.print(" [anton.muted]Thank you for helping us improve Anton.[/]")
console.print()
11 changes: 9 additions & 2 deletions anton/commands/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ def handle_theme(console: Console, arg: str) -> None:
elif arg in ("light", "dark"):
new_mode = arg
else:
console.print(f"[anton.warning]Unknown theme '{arg}'. Use: /theme light | /theme dark[/]")
console.print(
f"[anton.warning]Unknown theme '{arg}'. Use: /theme light | /theme dark[/]"
)
console.print()
return

Expand All @@ -37,7 +39,9 @@ def print_slash_help(console: Console) -> None:
console.print(" [bold]/llm[/] — Change LLM provider or API key")

console.print("\n[bold]Data Connections[/]")
console.print(" [bold]/connect[/] — Connect a database or API to your Local Vault")
console.print(
" [bold]/connect[/] — Connect a database or API to your Local Vault"
)
console.print(" [bold]/list[/] — List all saved connections")
console.print(" [bold]/edit[/] — Edit credentials for an existing connection")
console.print(" [bold]/remove[/] — Remove a saved connection")
Expand All @@ -54,6 +58,9 @@ def print_slash_help(console: Console) -> None:
console.print(" [bold]/publish[/] — Publish an HTML report to the web")
console.print(" [bold]/unpublish[/] — Remove a published report")

console.print("\n[bold]Support[/]")
console.print(" [bold]/report-bug[/] — Submit a bug report with diagnostic info")

console.print("\n[bold]General[/]")
console.print(" [bold]/help[/] — Show this help menu")
console.print(" [bold]exit[/] — Exit the chat")
Expand Down
17 changes: 14 additions & 3 deletions anton/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ def _build_env_files() -> list[str]:


class AntonSettings(BaseSettings):
model_config = {"env_prefix": "ANTON_", "env_file": _ENV_FILES, "env_file_encoding": "utf-8", "extra": "ignore"}
model_config = {
"env_prefix": "ANTON_",
"env_file": _ENV_FILES,
"env_file_encoding": "utf-8",
"extra": "ignore",
}

planning_provider: str = "anthropic"
planning_model: str = "claude-sonnet-4-6"
Expand All @@ -44,7 +49,9 @@ class AntonSettings(BaseSettings):

episodic_memory: bool = True # episodic memory archive — on by default

proactive_dashboards: bool = False # when True, build HTML dashboards; when False, CLI output only
proactive_dashboards: bool = (
False # when True, build HTML dashboards; when False, CLI output only
)

theme: str = "auto"

Expand All @@ -68,6 +75,7 @@ class AntonSettings(BaseSettings):

# Publish service (anton-services API Gateway)
publish_url: str = "https://4nton.ai"
bug_report_url: str | None = None # Defaults to publish_url if not set

@field_validator("minds_ssl_verify", mode="before")
@classmethod
Expand All @@ -81,7 +89,10 @@ def model_post_init(self, __context) -> None:
if (
self.minds_api_key
and not self.openai_api_key
and (self.planning_provider == "openai-compatible" or self.coding_provider == "openai-compatible")
and (
self.planning_provider == "openai-compatible"
or self.coding_provider == "openai-compatible"
)
):
self.openai_api_key = self.minds_api_key
if not self.openai_base_url:
Expand Down
Loading
Loading