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.
- common_utils.py +261 -0
- core/__init__.py +7 -0
- core/constants.py +57 -0
- core/state.py +25 -0
- create_page.py +288 -0
- fdev.py +258 -0
- flutter_dev-0.1.0.dist-info/METADATA +411 -0
- flutter_dev-0.1.0.dist-info/RECORD +30 -0
- flutter_dev-0.1.0.dist-info/WHEEL +5 -0
- flutter_dev-0.1.0.dist-info/entry_points.txt +5 -0
- flutter_dev-0.1.0.dist-info/licenses/LICENSE +21 -0
- flutter_dev-0.1.0.dist-info/top_level.txt +9 -0
- gemini_api.py +395 -0
- git_diff_output_editor.py +34 -0
- install_legacy.py +467 -0
- managers/__init__.py +69 -0
- managers/ai.py +113 -0
- managers/app.py +541 -0
- managers/brew.py +477 -0
- managers/build.py +436 -0
- managers/datetime.py +49 -0
- managers/device.py +207 -0
- managers/doctor.py +286 -0
- managers/git.py +981 -0
- managers/git_account.py +542 -0
- managers/merge.py +165 -0
- managers/mirror.py +205 -0
- managers/project.py +138 -0
- managers/web_deploy.py +43 -0
- switch_ai.py +181 -0
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)
|