Coverage for src/clauth/commands/init.py: 75%
100 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-28 14:45 -0400
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-28 14:45 -0400
1# Copyright (c) 2025 Mahmood Khordoo
2#
3# This software is licensed under the MIT License.
4# See the LICENSE file in the root directory for details.
6import subprocess
7import os
8import typer
9import clauth.aws_utils as aws
10from clauth.config import get_config_manager
11from clauth.helpers import (
12 ExecutableNotFoundError,
13 clear_screen,
14 get_app_path,
15 prompt_for_region_if_needed,
16 show_welcome_logo,
17 choose_auth_method,
18)
19from clauth.aws_utils import (
20 setup_sso_auth,
21 setup_iam_user_auth,
22)
23from rich.console import Console
24from InquirerPy import inquirer
25from InquirerPy import get_style
27app = typer.Typer()
28console = Console()
29env = os.environ.copy()
32def _handle_authentication(config, cli_overrides):
33 """Handles the logic for AWS authentication."""
34 auth_method = choose_auth_method()
35 typer.echo()
37 if not prompt_for_region_if_needed(config, cli_overrides):
38 raise typer.Exit(1)
40 if auth_method == "skip":
41 if not aws.user_is_authenticated(profile=config.aws.profile):
42 typer.secho(
43 "❌ No valid authentication found. Please choose an authentication method.",
44 fg=typer.colors.RED,
45 )
46 raise typer.Exit(1)
47 typer.secho(
48 f"✅ Already authenticated with AWS profile '{config.aws.profile}'",
49 fg=typer.colors.GREEN,
50 )
51 typer.echo("Skipping credential setup...")
52 elif auth_method == "iam":
53 if not setup_iam_user_auth(config.aws.profile, config.aws.region):
54 raise typer.Exit(1)
55 elif auth_method == "sso":
56 if not setup_sso_auth(config, cli_overrides):
57 raise typer.Exit(1)
60def _launch_claude_cli(config, env):
61 """Launches the Claude Code CLI with the given environment."""
62 try:
63 claude_path = get_app_path(config.cli.claude_cli_name)
64 clear_screen()
65 subprocess.run([claude_path], env=env, check=True)
66 except ExecutableNotFoundError as e:
67 typer.secho(f"Setup failed: {e}", fg=typer.colors.RED)
68 typer.secho(
69 "Please install Claude Code CLI and ensure it's in your PATH.",
70 fg=typer.colors.YELLOW,
71 )
72 raise typer.Exit(1)
73 except ValueError as e:
74 typer.secho(f"Configuration error: {e}", fg=typer.colors.RED)
75 raise typer.Exit(1)
78def _handle_model_selection(config, config_manager, console):
79 """Handles the logic for selecting Bedrock models."""
80 # Check if we have existing model configuration
81 if config.models.default_model_arn and config.models.fast_model_arn:
82 typer.echo("Found existing model configuration:")
83 typer.echo(f" Default model: {config.models.default_model}")
84 typer.echo(f" Small/Fast model: {config.models.fast_model}")
86 # Get custom style from config manager
87 custom_style = get_style(config_manager.get_custom_style())
89 use_existing = inquirer.confirm(
90 message="Use existing model configuration?",
91 default=True,
92 style=custom_style,
93 ).execute()
95 if use_existing:
96 model_id_default = config.models.default_model
97 model_id_fast = config.models.fast_model
98 model_map = {
99 model_id_default: config.models.default_model_arn,
100 model_id_fast: config.models.fast_model_arn,
101 }
102 typer.echo(f"Using saved models: {model_id_default}, {model_id_fast}")
103 return model_id_default, model_id_fast, model_map
105 # No existing configuration or user chose not to use it, do full model discovery
106 with console.status("[bold blue]Discovering available models...") as status:
107 model_ids, model_arns = aws.list_bedrock_profiles(
108 profile=config.aws.profile,
109 region=config.aws.region,
110 provider=config.models.provider_filter,
111 )
113 # Get custom style from config manager
114 custom_style = get_style(config_manager.get_custom_style())
116 model_id_default = inquirer.select(
117 message="Select your [default] model:",
118 instruction="↑↓ move • Enter select",
119 pointer="▶ ",
120 amark="✔",
121 choices=model_ids,
122 default=config.models.default_model
123 if config.models.default_model in model_ids
124 else (model_ids[0] if model_ids else None),
125 style=custom_style,
126 max_height="100%",
127 ).execute()
129 model_id_fast = inquirer.select(
130 message="Select your [small/fast] model (you can choose the same as default):",
131 instruction="↑↓ move • Enter select",
132 pointer="▶ ",
133 amark="✔",
134 choices=model_ids,
135 default=config.models.fast_model
136 if config.models.fast_model in model_ids
137 else (model_ids[-1] if model_ids else None),
138 style=custom_style,
139 max_height="100%",
140 ).execute()
142 model_map = {id: arn for id, arn in zip(model_ids, model_arns)}
144 # Save updated model selections to configuration
145 config_manager.update_model_settings(
146 default_model=model_id_default,
147 fast_model=model_id_fast,
148 default_arn=model_map[model_id_default],
149 fast_arn=model_map[model_id_fast],
150 )
151 return model_id_default, model_id_fast, model_map
154def init_command(
155 profile: str = typer.Option(
156 None,
157 "--profile",
158 "-p",
159 help="AWS profile to create or update (saved under [profile <name>] in ~/.aws/config).",
160 rich_help_panel="AWS Profile",
161 ),
162 session_name: str = typer.Option(
163 None,
164 "--session-name",
165 "-s",
166 help="Name of the SSO session to create (saved under [sso-session <name>] in ~/.aws/config).",
167 rich_help_panel="AWS SSO",
168 ),
169 sso_start_url: str = typer.Option(
170 None,
171 "--sso-start-url",
172 help="IAM Identity Center (SSO) Start URL (e.g., https://d-…awsapps.com/start/).",
173 rich_help_panel="AWS SSO",
174 ),
175 sso_region: str = typer.Option(
176 None,
177 "--sso-region",
178 help="Region that hosts your IAM Identity Center (SSO) instance.",
179 rich_help_panel="AWS SSO",
180 ),
181 region: str = typer.Option(
182 None,
183 "--region",
184 "-r",
185 help="Default AWS client region for this profile (used for STS/Bedrock calls).",
186 rich_help_panel="AWS Profile",
187 ),
188 auto_start: bool = typer.Option(
189 None,
190 "--auto-start/--no-auto-start",
191 help="Launch the Claude CLI immediately after successful setup.",
192 rich_help_panel="Behavior",
193 ),
194):
195 """
196 Interactive setup wizard for CLAUTH.
198 Configures AWS authentication (SSO or IAM user), discovers available Bedrock models,
199 and optionally launches Claude Code CLI with proper environment variables.
200 This is the main entry point for first-time CLAUTH setup.
202 Args:
203 profile: AWS profile name to create/update (default from config)
204 session_name: SSO session name (default from config, SSO only)
205 sso_start_url: IAM Identity Center start URL (default from config, SSO only)
206 sso_region: SSO region (default from config, SSO only)
207 region: Default AWS region for profile (default from config)
208 auto_start: Whether to launch Claude Code after setup (default from config)
209 """
210 # Load configuration and apply CLI overrides
211 config_manager = get_config_manager()
212 config = config_manager.load()
214 # Track which CLI parameters were provided
215 cli_overrides = {
216 "profile": profile is not None,
217 "session_name": session_name is not None,
218 "sso_start_url": sso_start_url is not None,
219 "sso_region": sso_region is not None,
220 "region": region is not None,
221 "auto_start": auto_start is not None,
222 }
224 # Override config with CLI parameters if provided
225 if profile is not None:
226 config.aws.profile = profile
227 if session_name is not None:
228 config.aws.session_name = session_name
229 if sso_start_url is not None:
230 config.aws.sso_start_url = sso_start_url
231 if sso_region is not None:
232 config.aws.sso_region = sso_region
233 if region is not None:
234 config.aws.region = region
235 if auto_start is not None:
236 config.cli.auto_start = auto_start
238 show_welcome_logo(console=console)
240 try:
241 typer.secho(
242 "Step 1/3 — Configuring AWS authentication...", fg=typer.colors.BLUE
243 )
244 typer.echo()
246 _handle_authentication(config, cli_overrides)
248 typer.secho("Step 2/3 — Configuring models...", fg=typer.colors.BLUE)
250 model_id_default, model_id_fast, model_map = _handle_model_selection(config, config_manager, console)
252 typer.echo(f"Default model: {model_id_default}")
253 typer.echo(f"Small/Fast model: {model_id_fast}")
255 env.update(
256 {
257 "AWS_PROFILE": config.aws.profile,
258 "AWS_REGION": config.aws.region,
259 "CLAUDE_CODE_USE_BEDROCK": "1",
260 "ANTHROPIC_MODEL": model_map[model_id_default],
261 "ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION": model_map[model_id_fast],
262 }
263 )
265 typer.echo(
266 f"default model: {model_id_default}\n small/fast model: {model_id_fast}\n"
267 )
269 if config.cli.auto_start:
270 typer.secho("Setup complete ✅", fg=typer.colors.GREEN)
271 typer.secho("Step 3/3 — Launching Claude Code...", fg=typer.colors.BLUE)
272 _launch_claude_cli(config, env)
273 else:
274 typer.secho("Step 3/3 — Setup complete.", fg=typer.colors.GREEN)
275 typer.echo("Run the Claude Code CLI when you're ready: ", nl=False)
276 typer.secho(config.cli.claude_cli_name, bold=True)
278 except subprocess.CalledProcessError as e:
279 typer.secho(f"Setup failed. Exit code: {e.returncode}", fg=typer.colors.RED)
280 exit(f"Failed to setup. Error Code: {e.returncode}")