flutter-dev 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
install_legacy.py ADDED
@@ -0,0 +1,467 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Cross-platform setup script for Flutter development tools
4
+ Works on macOS, Linux, and Windows
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import platform
10
+ import shutil
11
+ import subprocess
12
+ from pathlib import Path
13
+
14
+ # Colors for output
15
+ RED = '\033[0;31m'
16
+ GREEN = '\033[0;32m'
17
+ YELLOW = '\033[1;33m'
18
+ BLUE = '\033[0;34m'
19
+ NC = '\033[0m'
20
+
21
+ # Disable colors on Windows CMD (unless using Windows Terminal)
22
+ if platform.system() == "Windows" and not os.environ.get('WT_SESSION'):
23
+ RED = GREEN = YELLOW = BLUE = NC = ''
24
+
25
+ def get_system_info():
26
+ """Get system information"""
27
+ return {
28
+ 'platform': platform.system(),
29
+ 'home': Path.home(),
30
+ 'python': sys.executable
31
+ }
32
+
33
+ def get_base_python():
34
+ """Get the base Python executable (not venv Python)"""
35
+ # sys.base_executable gives the original Python even when in a venv
36
+ if hasattr(sys, 'base_executable') and sys.base_executable:
37
+ return sys.base_executable
38
+ return sys.executable
39
+
40
+ def get_dependencies_from_requirements():
41
+ """Read dependencies from requirements.txt file"""
42
+ requirements_file = Path(__file__).parent / 'requirements.txt'
43
+ dependencies = []
44
+
45
+ if requirements_file.exists():
46
+ with open(requirements_file, 'r', encoding='utf-8') as f:
47
+ for line in f:
48
+ line = line.strip()
49
+ # Skip empty lines and comments
50
+ if not line or line.startswith('#'):
51
+ continue
52
+ # Extract package name (remove version specifiers for import check)
53
+ package_name = line.split('>=')[0].split('==')[0].split('<')[0].strip()
54
+ if package_name:
55
+ dependencies.append(package_name)
56
+
57
+ # Fallback to hardcoded list if requirements.txt not found or empty
58
+ if not dependencies:
59
+ dependencies = ['python-dotenv', 'requests']
60
+
61
+ return dependencies
62
+
63
+
64
+ def install_dependencies():
65
+ """Install required Python dependencies to base Python"""
66
+ print(f"{YELLOW}Installing dependencies...{NC}")
67
+
68
+ base_python = get_base_python()
69
+
70
+ # First try to install from requirements.txt directly
71
+ requirements_file = Path(__file__).parent / 'requirements.txt'
72
+ if requirements_file.exists():
73
+ install_methods = [
74
+ [base_python, '-m', 'pip', 'install', '-r', str(requirements_file)],
75
+ [base_python, '-m', 'pip', 'install', '--user', '-r', str(requirements_file)],
76
+ [base_python, '-m', 'pip', 'install', '--break-system-packages', '-r', str(requirements_file)],
77
+ ]
78
+
79
+ for method in install_methods:
80
+ try:
81
+ subprocess.check_call(method, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
82
+ print(f"{GREEN}✓ Dependencies installed{NC}")
83
+ print()
84
+ return
85
+ except subprocess.CalledProcessError:
86
+ continue
87
+
88
+ # Fallback: Install packages individually
89
+ dependencies = get_dependencies_from_requirements()
90
+
91
+ for package in dependencies:
92
+ try:
93
+ __import__(package.replace('-', '_'))
94
+ print(f"{GREEN}✓ {package} is already installed{NC}")
95
+ except ImportError:
96
+ print(f"{YELLOW}Installing {package}...{NC}")
97
+
98
+ # Try different installation methods using base Python
99
+ install_methods = [
100
+ # Method 1: Standard pip install
101
+ [base_python, '-m', 'pip', 'install', package],
102
+ # Method 2: With --user flag
103
+ [base_python, '-m', 'pip', 'install', '--user', package],
104
+ # Method 3: With --break-system-packages (for externally managed environments)
105
+ [base_python, '-m', 'pip', 'install', '--break-system-packages', package],
106
+ ]
107
+
108
+ installed = False
109
+ for method in install_methods:
110
+ try:
111
+ subprocess.check_call(method,
112
+ stdout=subprocess.DEVNULL,
113
+ stderr=subprocess.DEVNULL)
114
+ print(f"{GREEN}✓ Installed {package}{NC}")
115
+ installed = True
116
+ break
117
+ except subprocess.CalledProcessError:
118
+ continue
119
+
120
+ if not installed:
121
+ print(f"{RED}✗ Failed to install {package}{NC}")
122
+ print(f"{YELLOW}→ Run: pip3 install --user {package}{NC}")
123
+
124
+ print()
125
+
126
+ def create_batch_wrapper(script_path, wrapper_path, script_name):
127
+ """Create Windows batch wrapper with base Python path"""
128
+ python_path = get_base_python()
129
+ batch_content = f'''@echo off
130
+ "{python_path}" "{script_path}" %*
131
+ '''
132
+ with open(wrapper_path, 'w', encoding='utf-8') as f:
133
+ f.write(batch_content)
134
+
135
+ def create_shell_wrapper(script_path, wrapper_path, script_name):
136
+ """Create Unix shell wrapper with base Python path"""
137
+ python_path = get_base_python()
138
+ shell_content = f'''#!/bin/bash
139
+ "{python_path}" "{script_path}" "$@"
140
+ '''
141
+ with open(wrapper_path, 'w', encoding='utf-8') as f:
142
+ f.write(shell_content)
143
+
144
+ # Make executable
145
+ os.chmod(wrapper_path, 0o755)
146
+
147
+ def copy_directory(src_dir, dest_dir, dir_name):
148
+ """Copy entire directory with all contents"""
149
+ src_path = src_dir / dir_name
150
+ dest_path = dest_dir / dir_name
151
+
152
+ if src_path.exists() and src_path.is_dir():
153
+ # Remove existing directory if exists
154
+ if dest_path.exists():
155
+ shutil.rmtree(dest_path)
156
+
157
+ # Copy entire directory
158
+ shutil.copytree(src_path, dest_path)
159
+
160
+ # Count files copied
161
+ file_count = sum(1 for _ in dest_path.rglob('*.py'))
162
+ print(f"{GREEN}✓ Copied {dir_name}/ directory ({file_count} Python files){NC}")
163
+ return True
164
+ return False
165
+
166
+ def setup_windows(system_info):
167
+ """Setup for Windows"""
168
+ print(f"\n{YELLOW}Setting up for Windows...{NC}")
169
+
170
+ home = system_info['home']
171
+ scripts_dir = home / 'scripts' / 'flutter-tools'
172
+ bin_dir = home / 'bin'
173
+
174
+ # Create directories
175
+ scripts_dir.mkdir(parents=True, exist_ok=True)
176
+ bin_dir.mkdir(parents=True, exist_ok=True)
177
+
178
+ # Define all Python scripts to copy
179
+ scripts_to_copy = [
180
+ ('fdev.py', 'fdev'),
181
+ ('create_page.py', 'create-page'),
182
+ ('gemini_api.py', 'gemini-api'),
183
+ ('git_diff_output_editor.py', 'git-diff-editor')
184
+ ]
185
+
186
+ # Define utility files that need to be copied (no command wrappers needed)
187
+ utility_files = ['common_utils.py', 'switch_ai.py', 'requirements.txt']
188
+
189
+ # Define module directories to copy
190
+ module_directories = ['core', 'managers']
191
+
192
+ # Copy scripts to scripts directory (if not already there)
193
+ current_dir = Path(__file__).parent
194
+
195
+ if current_dir != scripts_dir:
196
+ # Copy main scripts
197
+ for script_file, _ in scripts_to_copy:
198
+ if (current_dir / script_file).exists():
199
+ shutil.copy2(current_dir / script_file, scripts_dir / script_file)
200
+ print(f"{GREEN}✓ Copied {script_file}{NC}")
201
+
202
+ # Copy utility files
203
+ for util_file in utility_files:
204
+ if (current_dir / util_file).exists():
205
+ shutil.copy2(current_dir / util_file, scripts_dir / util_file)
206
+ print(f"{GREEN}✓ Copied {util_file}{NC}")
207
+
208
+ # Copy module directories (core/, managers/)
209
+ for module_dir in module_directories:
210
+ copy_directory(current_dir, scripts_dir, module_dir)
211
+
212
+ # Copy .env file if it exists in current directory
213
+ env_file_current = current_dir / '.env'
214
+ env_file_target = scripts_dir / '.env'
215
+
216
+ if env_file_current.exists():
217
+ shutil.copy2(env_file_current, env_file_target)
218
+ print(f"{GREEN}✓ Copied .env file{NC}")
219
+
220
+ # Copy .env.example if it exists
221
+ env_example = current_dir / '.env.example'
222
+ if env_example.exists():
223
+ shutil.copy2(env_example, scripts_dir / '.env.example')
224
+
225
+ # If .env was not copied and doesn't exist, create from example
226
+ if not env_file_current.exists() and not env_file_target.exists():
227
+ shutil.copy2(env_example, env_file_target)
228
+ print(f"{YELLOW}✓ Created .env from example - please add your API keys{NC}")
229
+ elif not env_file_current.exists():
230
+ print(f"{YELLOW}⚠️ Please create .env file and add your API keys{NC}")
231
+
232
+ # Create batch wrappers for all scripts
233
+ print(f"{GREEN}✓ Created command wrappers{NC}")
234
+ for script_file, command_name in scripts_to_copy:
235
+ script_path = scripts_dir / script_file
236
+ if script_path.exists():
237
+ create_batch_wrapper(script_path, bin_dir / f'{command_name}.bat', command_name)
238
+
239
+ def setup_unix(system_info):
240
+ """Setup for macOS/Linux"""
241
+ print(f"\n{YELLOW}Setting up for {system_info['platform']}...{NC}")
242
+
243
+ home = system_info['home']
244
+ scripts_dir = home / 'scripts' / 'flutter-tools'
245
+ bin_dir = home / 'bin'
246
+
247
+ # Create directories
248
+ scripts_dir.mkdir(parents=True, exist_ok=True)
249
+ bin_dir.mkdir(parents=True, exist_ok=True)
250
+
251
+ # Define all Python scripts to copy
252
+ scripts_to_copy = [
253
+ ('fdev.py', 'fdev'),
254
+ ('create_page.py', 'create-page'),
255
+ ('gemini_api.py', 'gemini-api'),
256
+ ('git_diff_output_editor.py', 'git-diff-editor')
257
+ ]
258
+
259
+ # Define utility files that need to be copied (no command wrappers needed)
260
+ utility_files = ['common_utils.py', 'switch_ai.py', 'requirements.txt']
261
+
262
+ # Define module directories to copy
263
+ module_directories = ['core', 'managers']
264
+
265
+ # Copy scripts to scripts directory (if not already there)
266
+ current_dir = Path(__file__).parent
267
+
268
+ if current_dir != scripts_dir:
269
+ # Copy main scripts
270
+ for script_file, _ in scripts_to_copy:
271
+ if (current_dir / script_file).exists():
272
+ shutil.copy2(current_dir / script_file, scripts_dir / script_file)
273
+ print(f"{GREEN}✓ Copied {script_file}{NC}")
274
+
275
+ # Copy utility files
276
+ for util_file in utility_files:
277
+ if (current_dir / util_file).exists():
278
+ shutil.copy2(current_dir / util_file, scripts_dir / util_file)
279
+ print(f"{GREEN}✓ Copied {util_file}{NC}")
280
+
281
+ # Copy module directories (core/, managers/)
282
+ for module_dir in module_directories:
283
+ copy_directory(current_dir, scripts_dir, module_dir)
284
+
285
+ # Copy .env file if it exists in current directory
286
+ env_file_current = current_dir / '.env'
287
+ env_file_target = scripts_dir / '.env'
288
+
289
+ if env_file_current.exists():
290
+ shutil.copy2(env_file_current, env_file_target)
291
+ print(f"{GREEN}✓ Copied .env file{NC}")
292
+
293
+ # Copy .env.example if it exists
294
+ env_example = current_dir / '.env.example'
295
+ if env_example.exists():
296
+ shutil.copy2(env_example, scripts_dir / '.env.example')
297
+
298
+ # If .env was not copied and doesn't exist, create from example
299
+ if not env_file_current.exists() and not env_file_target.exists():
300
+ shutil.copy2(env_example, env_file_target)
301
+ print(f"{YELLOW}✓ Created .env from example - please add your API keys{NC}")
302
+ elif not env_file_current.exists():
303
+ print(f"{YELLOW}⚠️ Please create .env file and add your API keys{NC}")
304
+
305
+ # Make all scripts executable
306
+ for script_file, _ in scripts_to_copy:
307
+ script_path = scripts_dir / script_file
308
+ if script_path.exists():
309
+ os.chmod(script_path, 0o755)
310
+
311
+ # Make module files executable too
312
+ for module_dir in ['core', 'managers']:
313
+ module_path = scripts_dir / module_dir
314
+ if module_path.exists():
315
+ for py_file in module_path.glob('*.py'):
316
+ os.chmod(py_file, 0o755)
317
+
318
+ # Create shell wrappers with absolute Python path
319
+ print(f"{GREEN}✓ Created command wrappers{NC}")
320
+ for script_file, command_name in scripts_to_copy:
321
+ script_path = scripts_dir / script_file
322
+ command_link = bin_dir / command_name
323
+
324
+ if script_path.exists():
325
+ # Remove existing symlink or file
326
+ if command_link.exists() or command_link.is_symlink():
327
+ command_link.unlink()
328
+ create_shell_wrapper(script_path, command_link, command_name)
329
+
330
+ def auto_configure_path(bin_dir, system_platform, home):
331
+ """Automatically configure PATH variable based on platform"""
332
+ path_env = os.environ.get('PATH', '')
333
+ bin_path_str = str(bin_dir)
334
+
335
+ # PATH already configured
336
+ if bin_path_str in path_env:
337
+ print(f"\n{GREEN}✓ PATH already configured{NC}")
338
+ return True
339
+
340
+ export_line = f'export PATH="$HOME/bin:$PATH"'
341
+
342
+ if system_platform == 'Windows':
343
+ # Windows: use PowerShell to set PATH permanently
344
+ print(f"\n{YELLOW}Configuring PATH for Windows...{NC}")
345
+ try:
346
+ # Use setx to permanently add to user PATH
347
+ current_path = subprocess.check_output(
348
+ ['powershell', '-Command',
349
+ '[Environment]::GetEnvironmentVariable("Path", "User")'],
350
+ text=True, stderr=subprocess.DEVNULL
351
+ ).strip()
352
+
353
+ if bin_path_str in current_path:
354
+ print(f"{GREEN}✓ PATH already contains {bin_dir}{NC}")
355
+ return True
356
+
357
+ new_path = f"{current_path};{bin_path_str}" if current_path else bin_path_str
358
+ subprocess.check_call(
359
+ ['powershell', '-Command',
360
+ f'[Environment]::SetEnvironmentVariable("Path", "{new_path}", "User")'],
361
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
362
+ )
363
+ print(f"{GREEN}✓ PATH configured automatically{NC}")
364
+ print(f"{GREEN} Added: {bin_dir}{NC}")
365
+ print(f"\n{YELLOW}⚠️ Restart your terminal to use the commands{NC}")
366
+ return True
367
+ except (subprocess.CalledProcessError, FileNotFoundError):
368
+ # PowerShell failed, show manual instructions
369
+ print(f"{YELLOW}Could not auto-configure PATH. Please add manually:{NC}")
370
+ print(f"\n{BLUE}Path to add: {GREEN}{bin_dir}{NC}")
371
+ print(f"\n{YELLOW}Option 1 - GUI:{NC}")
372
+ print(f" 1. Press {BLUE}Win + R{NC} → type {BLUE}sysdm.cpl{NC} → Enter")
373
+ print(f" 2. {BLUE}Environment Variables{NC} → User variables → {BLUE}Path{NC} → Edit")
374
+ print(f" 3. New → add: {GREEN}{bin_dir}{NC}")
375
+ print(f"\n{YELLOW}Option 2 - PowerShell (Admin):{NC}")
376
+ print(f'{BLUE}[Environment]::SetEnvironmentVariable("Path", $env:Path + ";{bin_dir}", "User"){NC}')
377
+ return False
378
+ else:
379
+ # macOS/Linux: detect shell config and auto-append
380
+ shell = os.environ.get('SHELL', '')
381
+ if 'zsh' in shell:
382
+ shell_config = home / '.zshrc'
383
+ elif 'bash' in shell:
384
+ shell_config = home / '.bashrc'
385
+ else:
386
+ shell_config = home / '.profile'
387
+
388
+ print(f"\n{YELLOW}Configuring PATH for {system_platform}...{NC}")
389
+
390
+ # Check if export line already exists in config file
391
+ if shell_config.exists():
392
+ config_content = shell_config.read_text(encoding='utf-8')
393
+ if '$HOME/bin' in config_content or str(bin_dir) in config_content:
394
+ print(f"{GREEN}✓ PATH entry already exists in {shell_config.name}{NC}")
395
+ print(f"{YELLOW}⚠️ Restart your terminal or run: {BLUE}source {shell_config}{NC}")
396
+ return True
397
+
398
+ # Append export line to shell config
399
+ try:
400
+ with open(shell_config, 'a', encoding='utf-8') as f:
401
+ f.write(f'\n# Flutter Development Tools - added by setup.py\n')
402
+ f.write(f'{export_line}\n')
403
+
404
+ print(f"{GREEN}✓ PATH configured automatically{NC}")
405
+ print(f"{GREEN} Added to: {shell_config}{NC}")
406
+ print(f"{GREEN} Entry: {export_line}{NC}")
407
+ print(f"\n{YELLOW}⚠️ Restart your terminal or run:{NC}")
408
+ print(f" {BLUE}source {shell_config}{NC}")
409
+ return True
410
+ except OSError as e:
411
+ print(f"{RED}Could not write to {shell_config}: {e}{NC}")
412
+ print(f"{YELLOW}Please add this line manually to {BLUE}{shell_config}{NC}:{NC}")
413
+ print(f" {GREEN}{export_line}{NC}")
414
+ return False
415
+
416
+ def main():
417
+ print(f"{BLUE}Flutter Development Tools - Setup{NC}")
418
+ print(f"{BLUE}================================={NC}")
419
+
420
+ system_info = get_system_info()
421
+ print(f"Platform: {GREEN}{system_info['platform']}{NC}")
422
+ print(f"Python: {GREEN}{system_info['python']}{NC}")
423
+
424
+ # Install required dependencies first
425
+ install_dependencies()
426
+
427
+ if system_info['platform'] == 'Windows':
428
+ setup_windows(system_info)
429
+ bin_dir = system_info['home'] / 'bin'
430
+ scripts_dir = system_info['home'] / 'scripts' / 'flutter-tools'
431
+ else:
432
+ setup_unix(system_info)
433
+ bin_dir = system_info['home'] / 'bin'
434
+ scripts_dir = system_info['home'] / 'scripts' / 'flutter-tools'
435
+
436
+ print(f"\n{GREEN}{'='*50}{NC}")
437
+ print(f"{GREEN}✓ Setup completed successfully!{NC}")
438
+ print(f"{GREEN}{'='*50}{NC}")
439
+
440
+ # Auto-configure PATH
441
+ auto_configure_path(bin_dir, system_info['platform'], system_info['home'])
442
+
443
+ # Show available commands
444
+ print(f"\n{BLUE}{'='*50}{NC}")
445
+ print(f"{BLUE}Available Commands:{NC}")
446
+ print(f"{BLUE}{'='*50}{NC}")
447
+ print(f" {GREEN}fdev apk{NC} - Build release APK")
448
+ print(f" {GREEN}fdev setup{NC} - Full Flutter project setup")
449
+ print(f" {GREEN}fdev commit{NC} - AI-powered git commit")
450
+ print(f" {GREEN}create-page user{NC} - Create Flutter page structure")
451
+ print(f" {GREEN}gemini-api{NC} - Multi-AI service CLI")
452
+
453
+ # AI configuration
454
+ env_file_path = scripts_dir / '.env'
455
+ print(f"\n{BLUE}{'='*50}{NC}")
456
+ print(f"{BLUE}AI Configuration:{NC}")
457
+ print(f"{BLUE}{'='*50}{NC}")
458
+ print(f"{YELLOW}Edit: {GREEN}{env_file_path}{NC}")
459
+ print(f" {YELLOW}1.{NC} Add your API key (Groq/Mistral/SambaNova/OpenRouter)")
460
+ print(f" {YELLOW}2.{NC} Set DEFAULT_AI_SERVICE")
461
+ print(f" {YELLOW}3.{NC} Run {BLUE}fdev commit{NC} to test")
462
+
463
+ print(f"\n{BLUE}Installation directory:{NC} {GREEN}{scripts_dir}{NC}")
464
+ print()
465
+
466
+ if __name__ == "__main__":
467
+ main()
managers/__init__.py ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Managers module - All manager functions
4
+ """
5
+
6
+ from .device import (
7
+ get_all_connected_devices,
8
+ select_device_if_multiple,
9
+ build_adb_cmd,
10
+ ensure_device_connected,
11
+ get_usb_devices,
12
+ select_usb_device,
13
+ )
14
+
15
+ from .build import (
16
+ build_apk,
17
+ build_apk_split_per_abi,
18
+ build_aab,
19
+ release_run,
20
+ )
21
+
22
+ from .git import (
23
+ create_and_push_tag,
24
+ smart_commit,
25
+ sync_branches,
26
+ deploy_to_deployment,
27
+ )
28
+
29
+ from .app import (
30
+ install_apk,
31
+ uninstall_app,
32
+ clear_app_data,
33
+ get_current_foreground_app,
34
+ )
35
+
36
+ from .project import (
37
+ full_setup,
38
+ cleanup_project,
39
+ generate_lang,
40
+ run_build_runner,
41
+ repair_cache,
42
+ update_pods,
43
+ create_page,
44
+ )
45
+
46
+ from .mirror import (
47
+ setup_wireless_adb,
48
+ launch_scrcpy,
49
+ )
50
+
51
+ from .merge import (
52
+ merge_files,
53
+ )
54
+
55
+ from .datetime import (
56
+ open_datetime_settings,
57
+ )
58
+
59
+ from .web_deploy import (
60
+ web_deploy,
61
+ )
62
+
63
+ from .doctor import (
64
+ run_doctor,
65
+ )
66
+
67
+ from .brew import (
68
+ brew_manager,
69
+ )
managers/ai.py ADDED
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ AI Service Manager
4
+ Handles AI service switching and status display
5
+ """
6
+
7
+ import sys
8
+ import os
9
+ from pathlib import Path
10
+
11
+ from common_utils import (
12
+ RED, GREEN, YELLOW, BLUE, NC, MAGENTA,
13
+ read_env_value,
14
+ update_env_value,
15
+ )
16
+
17
+ VALID_SERVICES = ['groq', 'mistral', 'sambanova', 'openrouter']
18
+
19
+
20
+ def get_env_file_path():
21
+ """Get the configured .env file path without assuming an install directory."""
22
+ xdg_config_home = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config"))
23
+ candidates = [
24
+ os.getenv("FLUTTER_DEV_ENV"),
25
+ xdg_config_home / "flutter-dev" / ".env",
26
+ Path.home() / ".config" / "flutter-dev" / ".env",
27
+ Path.home() / "scripts" / "flutter-tools" / ".env",
28
+ Path(__file__).resolve().parent.parent / ".env",
29
+ Path.cwd() / ".env",
30
+ ]
31
+
32
+ for candidate in candidates:
33
+ if candidate:
34
+ env_file = Path(candidate).expanduser()
35
+ if env_file.exists():
36
+ return str(env_file)
37
+
38
+ return ".env"
39
+
40
+
41
+ def show_ai_status():
42
+ """
43
+ Show current AI service and available options with interactive selection
44
+ """
45
+ env_file = get_env_file_path()
46
+ current = read_env_value("DEFAULT_AI_SERVICE", env_file)
47
+
48
+ print(f"\n{BLUE} Current AI:{NC} {GREEN}{current or 'Not configured'}{NC}\n")
49
+
50
+ print(f"{YELLOW} Available services:{NC}")
51
+ for i, service in enumerate(VALID_SERVICES, 1):
52
+ if service == current:
53
+ print(f" {GREEN}{i}. {service} (active){NC}")
54
+ else:
55
+ print(f" {BLUE}{i}. {service}{NC}")
56
+
57
+ print()
58
+
59
+ try:
60
+ choice = input(f"{MAGENTA} Select [1-{len(VALID_SERVICES)}]: {NC}").strip()
61
+
62
+ if not choice:
63
+ return
64
+
65
+ index = int(choice) - 1
66
+ if 0 <= index < len(VALID_SERVICES):
67
+ selected = VALID_SERVICES[index]
68
+ if selected == current:
69
+ print(f"\n{YELLOW} Already using {selected}{NC}")
70
+ else:
71
+ do_switch(current, selected, env_file)
72
+ else:
73
+ print(f"\n{RED} Invalid choice{NC}")
74
+ except ValueError:
75
+ print(f"\n{RED} Invalid input{NC}")
76
+ except KeyboardInterrupt:
77
+ print()
78
+
79
+
80
+ def do_switch(current, service, env_file):
81
+ """Perform the actual switch"""
82
+ if update_env_value("DEFAULT_AI_SERVICE", service, env_file):
83
+ print(f"\n{GREEN} Switched: {BLUE}{current or 'none'}{NC} → {GREEN}{service}{NC}")
84
+ else:
85
+ print(f"\n{RED} Failed to switch{NC}")
86
+
87
+
88
+ def switch_ai_service(service):
89
+ """
90
+ Switch to specified AI service directly
91
+
92
+ Parameters:
93
+ service: Service name to switch to
94
+ """
95
+ service = service.lower()
96
+
97
+ if service not in VALID_SERVICES:
98
+ print(f"{RED}Error: Invalid service '{service}'{NC}")
99
+ print(f"{YELLOW}Valid options: {', '.join(VALID_SERVICES)}{NC}")
100
+ sys.exit(1)
101
+
102
+ env_file = get_env_file_path()
103
+ current = read_env_value("DEFAULT_AI_SERVICE", env_file)
104
+
105
+ if current == service:
106
+ print(f"{YELLOW}Already using: {GREEN}{service}{NC}")
107
+ return
108
+
109
+ if update_env_value("DEFAULT_AI_SERVICE", service, env_file):
110
+ print(f"{GREEN}Switched: {BLUE}{current or 'none'}{NC} → {GREEN}{service}{NC}")
111
+ else:
112
+ print(f"{RED}Failed to switch{NC}")
113
+ sys.exit(1)