tiphys.cli.main

Tiphys CLI main entry point.

Provides commands for running the gateway, sending messages, and diagnostics.

  1"""
  2Tiphys CLI main entry point.
  3
  4Provides commands for running the gateway, sending messages, and diagnostics.
  5"""
  6
  7import asyncio
  8import sys
  9from pathlib import Path
 10
 11import click
 12import structlog
 13import uvicorn
 14
 15from tiphys.config import load_config
 16from tiphys.logging import setup_logging
 17
 18logger = structlog.get_logger(__name__)
 19
 20
 21@click.group()
 22@click.option(
 23    "--log-level", type=click.Choice(["debug", "info", "warning", "error"]), default="info"
 24)
 25@click.pass_context
 26def cli(ctx: click.Context, log_level: str) -> None:
 27    """Tiphys - Local-first, extensible AI agent framework."""
 28    from tiphys.config import LogLevel
 29
 30    ctx.ensure_object(dict)
 31    config = load_config()
 32
 33    # Convert string to LogLevel enum
 34    level_enum = LogLevel(log_level.upper())
 35    setup_logging(level=level_enum, json_output=config.log_json)
 36    ctx.obj["config"] = config
 37
 38
 39@cli.command()
 40@click.option("--host", help="Host to bind to")
 41@click.option("--port", type=int, help="Port to bind to")
 42@click.option("--reload/--no-reload", default=False, help="Enable auto-reload")
 43@click.pass_context
 44def gateway(ctx: click.Context, host: str | None, port: int | None, reload: bool) -> None:
 45    """Start the Tiphys gateway server."""
 46    config = ctx.obj["config"]
 47    host = host or config.host
 48    port = port or config.port
 49
 50    click.echo(f"Starting Tiphys gateway on {host}:{port}...")
 51
 52    uvicorn.run(
 53        "tiphys.main:app",
 54        host=host,
 55        port=port,
 56        reload=reload,
 57        log_level="warning",
 58    )
 59
 60
 61@cli.command()
 62@click.argument("message")
 63@click.option("--session", help="Session key")
 64@click.pass_context
 65def agent(ctx: click.Context, message: str, session: str | None) -> None:
 66    """Send a message to the agent."""
 67    from tiphys.agent.runner import AgentRunner
 68    from tiphys.bootstrap import create_app_context
 69
 70    async def run_agent() -> None:
 71        app_ctx = await create_app_context(config=ctx.obj["config"])
 72        runner = AgentRunner(context=app_ctx)
 73
 74        session_key = session or "cli-session"
 75
 76        click.echo(f"Sending message: {message}")
 77
 78        async for block in runner.stream(message=message, session_key=session_key):
 79            if block.type == "text":
 80                click.echo(block.content, nl=False)
 81
 82        click.echo("\n")
 83        await app_ctx.shutdown()
 84
 85    asyncio.run(run_agent())
 86
 87
 88@cli.command()
 89def doctor() -> None:
 90    """Run diagnostics and health checks."""
 91    click.echo("Running Tiphys doctor...")
 92
 93    # 1. Check Python version
 94    click.echo(f"Python: {sys.version}")
 95
 96    # 2. Check config
 97    config = load_config()
 98    click.echo(f"Config: Loaded (Agent: {config.agent_name})")
 99
100    # 3. Check directories
101    data_dir = config.data_dir
102    click.echo(f"Data directory: {data_dir} ({'Exists' if data_dir.exists() else 'Missing'})")
103
104    # 4. Check LLM connection
105    click.echo("Checking LLM connection...")
106    from tiphys.agent.llm import LLMClient, Message
107
108    async def check_llm() -> None:
109        client = LLMClient.from_config(config)
110        try:
111            response = await client.complete([Message(role="user", content="ping")])
112            click.echo(
113                f"LLM: OK ({response.usage['total_tokens'] if response.usage else 'unknown'} tokens used)"
114            )
115        except Exception as e:
116            click.echo(f"LLM: Failed ({e})")
117
118    asyncio.run(check_llm())
119
120    click.echo("Doctor finished.")
121
122
123@cli.command()
124@click.pass_context
125def onboard(ctx: click.Context) -> None:
126    """Interactive setup wizard."""
127    click.echo("Welcome to Tiphys! 🚀")
128    click.echo("This wizard will help you configure your first agent.\n")
129
130    # 1. LLM Provider
131    provider = click.prompt(
132        "Choose LLM provider",
133        type=click.Choice(["openrouter", "openai", "anthropic", "ollama"]),
134        default="openrouter",
135    )
136
137    # 2. API Key (if not local)
138    api_key = ""
139    if provider != "ollama":
140        api_key = click.prompt("Enter API Key", hide_input=True)
141
142    # 3. Model
143    default_model = {
144        "openrouter": "anthropic/claude-3.5-sonnet",
145        "openai": "gpt-4o",
146        "anthropic": "claude-3-5-sonnet-20240620",
147        "ollama": "llama3",
148    }[provider]
149
150    model = click.prompt("Enter model name", default=default_model)
151
152    # 4. Agent Name
153    agent_name = click.prompt("Name your agent", default="Tiphys")
154
155    # 5. Telegram (Optional)
156    telegram_token = click.prompt(
157        "Telegram Bot Token (press Enter to skip)",
158        default="",
159        hide_input=True,
160    )
161
162    # Generate .env content
163    env_content = f"""# Tiphys Configuration
164TIPHYS_LLM_PROVIDER={provider}
165TIPHYS_MODEL={model}
166TIPHYS_AGENT_NAME={agent_name}
167"""
168    if api_key:
169        provider.upper()
170        if provider == "openrouter":
171            env_content += f"OPENROUTER_API_KEY={api_key}\n"
172        elif provider == "openai":
173            env_content += f"OPENAI_API_KEY={api_key}\n"
174        elif provider == "anthropic":
175            env_content += f"ANTHROPIC_API_KEY={api_key}\n"
176
177    if telegram_token:
178        env_content += f"TIPHYS_TELEGRAM_TOKEN={telegram_token}\n"
179
180    # Write to .env
181    env_path = Path(".env")
182    if env_path.exists():
183        if click.confirm(f"{env_path} already exists. Overwrite?", default=False):
184            env_path.write_text(env_content)
185            click.echo(f"\n✅ Configuration saved to {env_path}")
186        else:
187            click.echo("\n⚠️  Skipped saving .env")
188            click.echo("Here is the generated config:\n")
189            click.echo(env_content)
190    else:
191        env_path.write_text(env_content)
192        click.echo(f"\n✅ Configuration saved to {env_path}")
193
194    click.echo("\nSetup complete! You can now run:")
195    click.echo("  tiphys gateway")
196
197
198@cli.group()
199def tasks() -> None:
200    """Manage scheduled tasks."""
201    pass
202
203
204@tasks.command(name="list")
205@click.pass_context
206def list_tasks(ctx: click.Context) -> None:
207    """List all scheduled tasks."""
208    from tiphys.scheduler.store import TaskStore
209
210    store = TaskStore()
211    tasks = store.list_all()
212
213    if not tasks:
214        click.echo("No tasks found.")
215        return
216
217    click.echo(f"{'ID':<36} {'NAME':<20} {'STATUS':<10} {'NEXT RUN':<20}")
218    click.echo("-" * 90)
219    for t in tasks:
220        next_run = t.next_run.isoformat() if t.next_run else "N/A"
221        click.echo(f"{t.id:<36} {t.name[:20]:<20} {t.status:<10} {next_run:<20}")
222
223
224if __name__ == "__main__":
225    cli(obj={})
logger = <BoundLoggerLazyProxy(logger=None, wrapper_class=None, processors=None, context_class=None, initial_values={}, logger_factory_args=('tiphys.cli.main',))>
cli = <Group cli>

Tiphys - Local-first, extensible AI agent framework.

gateway = <Command gateway>

Start the Tiphys gateway server.

agent = <Command agent>

Send a message to the agent.

doctor = <Command doctor>

Run diagnostics and health checks.

onboard = <Command onboard>

Interactive setup wizard.

tasks = <Group tasks>

Manage scheduled tasks.

list_tasks = <Command list>

List all scheduled tasks.