Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
80 changes: 66 additions & 14 deletions anton/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import typer
from rich.console import Console
from rich.live import Live
from rich.panel import Panel
from rich.prompt import Confirm
from rich.spinner import Spinner
from rich.table import Table
Expand Down Expand Up @@ -175,9 +176,26 @@ def _ensure_dependencies(console: Console) -> None:
raise typer.Exit(1)


def _ensure_terms_consent(console: Console, settings) -> None:
"""Show terms acceptance screen on first run and persist the choice."""
# Clear screen
_TERMS_SUMMARY = (
"[bold]What you should know[/]\n\n"
" • Anton runs on your machine. Your code, files, and queries\n"
" stay local unless you explicitly ask Anton to send them.\n\n"
" • Anton sends anonymous usage events (e.g. session started)\n"
" to MindsDB to help us improve. No code or query content is\n"
" included. Disable with [anton.cyan]ANTON_ANALYTICS_ENABLED=false[/].\n\n"
" • When Anton uses an LLM provider you configure (OpenAI,\n"
" Anthropic, Minds, etc.), the prompts you send go to that\n"
" provider and are governed by their own terms.\n\n"
" • You're responsible for what Anton does on your behalf —\n"
" review proposed actions before authorizing them.\n\n"
"[bold]Full text[/]\n\n"
" Terms: [link=https://mindsdb.com/terms]https://mindsdb.com/terms[/]\n"
" Privacy: [link=https://mindsdb.com/privacy-policy]https://mindsdb.com/privacy-policy[/]\n"
)


def _render_terms_screen(console: Console) -> None:
"""Render the welcome banner + policy summary panel."""
os.system("cls" if sys.platform == "win32" else "clear")

logo = "A N T O N"
Expand All @@ -192,21 +210,55 @@ def _ensure_terms_consent(console: Console, settings) -> None:
" and accept our Anton policies."
)
console.print()
console.print(
Panel(
_TERMS_SUMMARY,
title="Anton — Terms & Privacy",
title_align="left",
border_style="anton.cyan",
padding=(1, 2),
)
)
console.print()

if Confirm.ask(
" Would you like to read the policies?", default=True, console=console
):
webbrowser.open("https://mindsdb.com/terms")
webbrowser.open("https://mindsdb.com/privacy-policy")

def _show_full_policies(console: Console) -> None:
"""Render the full Terms + Privacy Policy through the system pager."""
from rich.markdown import Markdown
from rich.rule import Rule

from anton.policies import PRIVACY_POLICY_MD, TERMS_OF_USE_MD

with console.pager(styles=True):
console.print(Markdown(TERMS_OF_USE_MD))
console.print()
console.print(" [anton.muted]Policies opened in your browser.[/]")
console.print(Rule(style="anton.cyan"))
console.print()
console.print(Markdown(PRIVACY_POLICY_MD))

accepted = Confirm.ask(
" Do you accept the Terms and Privacy Policy?",
default=True,
console=console,
)

def _ensure_terms_consent(console: Console, settings) -> None:
"""Show terms acceptance screen on first run and persist the choice."""
from rich.prompt import Prompt

accepted: bool | None = None
while accepted is None:
_render_terms_screen(console)
choice = Prompt.ask(
" Accept Terms and Privacy Policy? "
"[bold]y[/]es / [bold]n[/]o / [bold]s[/]how full text",
choices=["y", "n", "s"],
default="y",
show_choices=False,
console=console,
).lower()

if choice == "y":
accepted = True
elif choice == "n":
accepted = False
elif choice == "s":
_show_full_policies(console)

if not accepted:
console.print()
Expand Down
148 changes: 148 additions & 0 deletions anton/core/dispatch/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"""Anton Dispatch — bring agents to messaging platforms safely.

Dispatch is Anton's bridge between external messaging platforms (Telegram,
Slack, Discord, Gmail, CLI, …) and agent runtimes. It combines:

- **Channel adapters + isolation modes** — modeled on nanoclaw's design:
pluggable adapters, three isolation levels (shared session /
same-agent-separate-sessions / separate agent groups), per-session
SQLite as the single IO surface.
- **Cowork-style safety** — modeled on Claude Cowork's permission model:
declarative :class:`PermissionPolicy` per agent group, action-card
prompts for destructive actions, conservative defaults for scheduled
dispatch.

Public surface:

- :class:`ChannelAdapter`, :class:`InboundEvent`, :class:`OutboundMessage`,
:class:`ActionCard`, :class:`ActionResponse` — the adapter contract.
- :class:`AgentGroup`, :class:`MessagingGroup`, :class:`MessagingGroupAgent`,
:class:`Session`, :class:`SessionMode`, :class:`TriggerRule` — entities.
- :class:`PermissionPolicy`, :class:`GateDecision`, :func:`evaluate` —
safety gates.
- :class:`SQLiteSessionStore`, :class:`SessionStoreProtocol`,
:func:`open_store` — message persistence.
- :class:`DispatchRouter`, :class:`DispatchRepository`,
:class:`RuntimeOrchestrator`, :func:`matches_trigger` — orchestration.
- :class:`SqliteDispatchRepository` — concrete repository.
- :class:`InProcessRuntimeOrchestrator` — concrete runtime for in-process
agents (tests, CLI demos).
- :func:`register_channel_adapter`, :func:`init_channel_adapters` —
adapter discovery.
"""

from anton.core.dispatch.adapter import (
ActionCard,
ActionOption,
ActionResponse,
Attachment,
ChannelAdapter,
ChannelSetup,
InboundEvent,
InboundMessage,
MessageKind,
OutboundMessage,
PlatformAddress,
)
from anton.core.dispatch.entities import (
AgentGroup,
MessagingGroup,
MessagingGroupAgent,
Session,
SessionMode,
TriggerRule,
)
from anton.core.dispatch.local_runtime import (
AgentCallable,
InProcessRuntimeOrchestrator,
LocalScratchpadOrchestrator,
)
from anton.core.dispatch.policy import (
FileScope,
GateDecision,
GateResult,
PermissionPolicy,
ProposedAction,
evaluate,
)
from anton.core.dispatch.registry import (
AdapterFactory,
ChannelRegistration,
get_active_adapter,
get_active_adapters,
get_registered_channel_types,
init_channel_adapters,
register_channel_adapter,
shutdown_channel_adapters,
)
from anton.core.dispatch.repository import SqliteDispatchRepository
from anton.core.dispatch.router import (
DispatchRepository,
DispatchRouter,
PendingAction,
RuntimeOrchestrator,
matches_trigger,
)
from anton.core.dispatch.session_store import (
Direction,
SQLiteSessionStore,
SessionStoreProtocol,
StoredMessage,
open_store,
)

__all__ = [
# adapter
"ActionCard",
"ActionOption",
"ActionResponse",
"Attachment",
"ChannelAdapter",
"ChannelSetup",
"InboundEvent",
"InboundMessage",
"MessageKind",
"OutboundMessage",
"PlatformAddress",
# entities
"AgentGroup",
"MessagingGroup",
"MessagingGroupAgent",
"Session",
"SessionMode",
"TriggerRule",
# policy
"FileScope",
"GateDecision",
"GateResult",
"PermissionPolicy",
"ProposedAction",
"evaluate",
# registry
"AdapterFactory",
"ChannelRegistration",
"get_active_adapter",
"get_active_adapters",
"get_registered_channel_types",
"init_channel_adapters",
"register_channel_adapter",
"shutdown_channel_adapters",
# repository
"SqliteDispatchRepository",
# router
"DispatchRepository",
"DispatchRouter",
"PendingAction",
"RuntimeOrchestrator",
"matches_trigger",
# local_runtime
"AgentCallable",
"InProcessRuntimeOrchestrator",
"LocalScratchpadOrchestrator",
# session_store
"Direction",
"SQLiteSessionStore",
"SessionStoreProtocol",
"StoredMessage",
"open_store",
]
Loading
Loading