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.