diff --git a/kalibr/cli/signup_cmd.py b/kalibr/cli/signup_cmd.py index 4d1495e..c38521e 100644 --- a/kalibr/cli/signup_cmd.py +++ b/kalibr/cli/signup_cmd.py @@ -1,7 +1,6 @@ -"""kalibr signup - Create a Kalibr account from the CLI.""" +"""kalibr signup - Create a Kalibr account from the CLI (agent-driven onboarding).""" import os -import time import requests import typer @@ -12,19 +11,19 @@ def signup( - email: str = typer.Argument(..., help="Real email address for the account owner — a verification email will be sent here"), - org_name: str = typer.Option(None, "--org", help="Organization name (default: email prefix)"), + email: str = typer.Argument(..., help="Real email address for the account owner — credentials are returned immediately and a claim link is sent to this address"), + agent_name: str = typer.Option("kalibr-agent", "--agent-name", help="Name for this agent (for identification in the dashboard)"), + org_name: str = typer.Option(None, "--org", help="(unused, kept for backward compat)"), ) -> None: - """Create a Kalibr account and get API credentials. Recommended for agent-driven onboarding. Agent runs this with the real user email, human clicks one verification link.""" + """Create a Kalibr account and get API credentials immediately. Recommended for agent-driven onboarding.""" console.print(f"[bold]Creating Kalibr account for {email}...[/bold]") - # Start signup try: resp = requests.post( - f"{BACKEND_URL}/api/cli-auth/signup", - json={"email": email, "org_name": org_name}, - timeout=15, + f"{BACKEND_URL}/api/cli-auth/signup-and-provision", + json={"human_email": email, "agent_name": agent_name}, + timeout=20, ) if resp.status_code == 409: @@ -36,79 +35,56 @@ def signup( console.print("[red]Too many signup attempts. Try again in an hour.[/red]") raise typer.Exit(1) - resp.raise_for_status() + if resp.status_code != 200: + console.print(f"[red]Signup failed (HTTP {resp.status_code}): {resp.text[:200]}[/red]") + raise typer.Exit(1) + data = resp.json() + except requests.RequestException as e: console.print(f"[red]Signup failed: {e}[/red]") raise typer.Exit(1) - signup_id = data["signup_id"] - console.print(f"\n[green]✓ Verification email sent to {email}[/green]") - console.print("[cyan]Click the link in your email to activate your account...[/cyan]\n") - - # Poll for verification - poll_url = f"{BACKEND_URL}/api/cli-auth/signup/{signup_id}/status" - max_wait = 300 # 5 minutes - start = time.time() - - with console.status("[bold cyan]Waiting for email verification...") as spinner: - while time.time() - start < max_wait: - try: - resp = requests.get(poll_url, timeout=10) - if resp.status_code == 200: - result = resp.json() - - if result["status"] == "verified": - api_key = result["api_key"] - tenant_id = result["tenant_id"] - - # Write to .env - env_path = os.path.join(os.getcwd(), ".env") - lines = [] - if os.path.exists(env_path): - with open(env_path) as f: - lines = f.readlines() - - key_map = { - "KALIBR_API_KEY": api_key, - "KALIBR_TENANT_ID": tenant_id, - } - updated = set() - new_lines = [] - for line in lines: - replaced = False - for k, v in key_map.items(): - if line.startswith(f"{k}="): - new_lines.append(f"{k}={v}\n") - updated.add(k) - replaced = True - break - if not replaced: - new_lines.append(line) - - for k, v in key_map.items(): - if k not in updated: - new_lines.append(f"{k}={v}\n") - - with open(env_path, "w") as f: - f.writelines(new_lines) - - console.print("\n[bold green]✓ Account created![/bold green]") - console.print(f" API Key: {api_key[:12]}...") - console.print(f" Tenant ID: {tenant_id}") - console.print(f" Saved to: {env_path}") - console.print("\n[bold]Next: run 'kalibr init' to instrument your code[/bold]") - return - - elif result["status"] == "expired": - console.print("\n[red]Signup expired. Please try again.[/red]") - raise typer.Exit(1) - - except requests.RequestException: - pass # Retry on network errors - - time.sleep(5) - - console.print("\n[red]Timed out waiting for email verification (5 minutes).[/red]") - console.print("[yellow]Check your inbox and spam folder, then run 'kalibr signup' again.[/yellow]") - raise typer.Exit(1) + api_key = data["api_key"] + tenant_id = data["tenant_id"] + claim_url = data.get("claim_url") + + # Write to .env + env_path = os.path.join(os.getcwd(), ".env") + lines = [] + if os.path.exists(env_path): + with open(env_path) as f: + lines = f.readlines() + + key_map = { + "KALIBR_API_KEY": api_key, + "KALIBR_TENANT_ID": tenant_id, + } + updated = set() + new_lines = [] + for line in lines: + replaced = False + for k, v in key_map.items(): + if line.startswith(f"{k}="): + new_lines.append(f"{k}={v}\n") + updated.add(k) + replaced = True + break + if not replaced: + new_lines.append(line) + + for k, v in key_map.items(): + if k not in updated: + new_lines.append(f"{k}={v}\n") + + with open(env_path, "w") as f: + f.writelines(new_lines) + + console.print("\n[bold green]✓ Account created![/bold green]") + console.print(f" API Key: {api_key[:12]}...") + console.print(f" Tenant ID: {tenant_id}") + console.print(f" Saved to: {env_path}") + if claim_url: + console.print(f"\n[cyan]Dashboard access:[/cyan] {claim_url}") + console.print("[dim]Share this link with the account owner to claim the dashboard.[/dim]") + console.print("\n[bold]Next: run kalibr verify to confirm routing is live[/bold]")