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.
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ create-page = create_page:main
3
+ fdev = fdev:main
4
+ gemini-api = gemini_api:main
5
+ git-diff-editor = git_diff_output_editor:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Maxcode
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ common_utils
2
+ core
3
+ create_page
4
+ fdev
5
+ gemini_api
6
+ git_diff_output_editor
7
+ install_legacy
8
+ managers
9
+ switch_ai
gemini_api.py ADDED
@@ -0,0 +1,395 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Multi AI Service API for generating git commit messages
5
+ Supports: Groq, Mistral, SambaNova, OpenRouter
6
+ """
7
+
8
+ import requests
9
+ import json
10
+ import os
11
+ import re
12
+ import sys
13
+ import time
14
+ from pathlib import Path
15
+ from dotenv import load_dotenv
16
+
17
+ # Import common utilities
18
+ from common_utils import RED, GREEN, YELLOW, BLUE, NC
19
+
20
+ def find_env_file():
21
+ """Find the tool .env file without assuming a hardcoded install path."""
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() / "scripts" / "flutter-tools" / ".env",
27
+ Path(__file__).resolve().parent / ".env",
28
+ Path.cwd() / ".env",
29
+ ]
30
+
31
+ for candidate in candidates:
32
+ if candidate:
33
+ env_file = Path(candidate).expanduser()
34
+ if env_file.exists():
35
+ return env_file
36
+
37
+ return None
38
+
39
+
40
+ # Load environment variables from .env file when available.
41
+ env_path = find_env_file()
42
+ if env_path:
43
+ load_dotenv(env_path)
44
+ else:
45
+ load_dotenv()
46
+
47
+ VALID_AI_SERVICES = ("groq", "mistral", "sambanova", "openrouter")
48
+
49
+
50
+ def get_default_ai_service():
51
+ """Return the selected AI service without validating at import time."""
52
+ return os.getenv("DEFAULT_AI_SERVICE", "groq").lower()
53
+
54
+
55
+ def get_ai_configs():
56
+ """Build AI service configurations from the current environment."""
57
+ return {
58
+ "groq": {
59
+ "api_key": os.getenv("GROQ_API_KEY", ""),
60
+ "api_url": os.getenv("GROQ_API_URL", ""),
61
+ "model": os.getenv("GROQ_MODEL", "")
62
+ },
63
+ "mistral": {
64
+ "api_key": os.getenv("MISTRAL_API_KEY", ""),
65
+ "api_url": os.getenv("MISTRAL_API_URL", ""),
66
+ "model": os.getenv("MISTRAL_MODEL", "")
67
+ },
68
+ "sambanova": {
69
+ "api_key": os.getenv("SAMBANOVA_API_KEY", ""),
70
+ "api_url": os.getenv("SAMBANOVA_API_URL", ""),
71
+ "model": os.getenv("SAMBANOVA_MODEL", "")
72
+ },
73
+ "openrouter": {
74
+ "api_key": os.getenv("OPENROUTER_API_KEY", ""),
75
+ "api_url": os.getenv("OPENROUTER_API_URL", ""),
76
+ "model": os.getenv("OPENROUTER_MODEL", "")
77
+ }
78
+ }
79
+
80
+
81
+ def get_current_config():
82
+ """Validate and return the selected AI service config when it is needed."""
83
+ service = get_default_ai_service()
84
+ configs = get_ai_configs()
85
+
86
+ if service not in configs:
87
+ print(f"{RED}✗ Error: Invalid AI service '{service}'{NC}")
88
+ print(f"{YELLOW}→ Available services: {', '.join(configs.keys())}{NC}")
89
+ return service, None
90
+
91
+ config = configs[service]
92
+ if not config["api_key"]:
93
+ print(f"{RED}✗ Error: API key not found for {service.upper()}{NC}")
94
+ print(f"{YELLOW}→ Please set {service.upper()}_API_KEY in ~/.config/flutter-dev/.env{NC}")
95
+ return service, None
96
+
97
+ if not config["api_url"] or not config["model"]:
98
+ print(f"{RED}✗ Error: API URL/model not configured for {service.upper()}{NC}")
99
+ print(f"{YELLOW}→ Please set {service.upper()}_API_URL and {service.upper()}_MODEL{NC}")
100
+ return service, None
101
+
102
+ return service, config
103
+
104
+
105
+ DEFAULT_AI_SERVICE = get_default_ai_service()
106
+ AI_CONFIGS = get_ai_configs()
107
+
108
+
109
+ def strip_markdown_code_blocks(text):
110
+ """
111
+ Remove markdown code blocks from AI response.
112
+ Handles: ```text, ```markdown, ``` etc.
113
+ """
114
+ text = text.strip()
115
+
116
+ # Pattern to match code blocks with optional language identifier
117
+ # Matches: ```text\n...\n```, ```markdown\n...\n```, ```\n...\n```
118
+ pattern = r'^```(?:text|markdown|commit)?\s*\n?(.*?)\n?```$'
119
+ match = re.match(pattern, text, re.DOTALL | re.IGNORECASE)
120
+
121
+ if match:
122
+ return match.group(1).strip()
123
+
124
+ # Also handle case where code block markers are on separate lines
125
+ lines = text.split('\n')
126
+ if lines and lines[0].strip().startswith('```'):
127
+ # Remove first line if it's a code block start
128
+ lines = lines[1:]
129
+ if lines and lines[-1].strip() == '```':
130
+ # Remove last line if it's a code block end
131
+ lines = lines[:-1]
132
+
133
+ return '\n'.join(lines).strip()
134
+
135
+
136
+ def generate_commit_message(git_diff_content):
137
+ """
138
+ Generate commit message using selected AI service based on git diff
139
+ """
140
+ service, config = get_current_config()
141
+ if not config:
142
+ return None
143
+
144
+ prompt = f"""Based on the following git diff, generate a commit message following the Angular Conventional Commit format:
145
+
146
+ <type>(<scope>): <short summary>
147
+ <blank line>
148
+ <body>
149
+ <blank line>
150
+ <footer>
151
+
152
+ Rules:
153
+ - Use one of these types: feat, fix, docs, style, refactor, test, chore
154
+ - (scope) is optional but should describe the module/component (e.g., auth, cart, ui)
155
+ - Summary should be short (max 50 chars) and in imperative form
156
+ - Body (optional) should explain what and why, not how
157
+ - Footer (optional) should include BREAKING CHANGE or issue references if applicable
158
+ - Use proper emoticons where appropriate
159
+ - Don't give any long boring texts, STRICTLY no explanations needed
160
+
161
+ Git diff content:
162
+ {git_diff_content}
163
+
164
+ Return only the commit message without any additional text or explanations."""
165
+
166
+ payload = {
167
+ "model": config["model"],
168
+ "messages": [
169
+ {
170
+ "role": "user",
171
+ "content": prompt
172
+ }
173
+ ],
174
+ "max_tokens": 500,
175
+ "temperature": 0.7
176
+ }
177
+
178
+ headers = {
179
+ "Content-Type": "application/json",
180
+ "Authorization": f"Bearer {config['api_key']}",
181
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
182
+ }
183
+
184
+ max_retries = 3
185
+ retry_delay = 2
186
+
187
+ for attempt in range(max_retries):
188
+ try:
189
+ # Print context information
190
+ diff_size = len(git_diff_content)
191
+ print(f"{BLUE}→ Git diff size: {diff_size} characters{NC}")
192
+ print(f"{YELLOW}Generating commit message using {service.upper()} AI ({config['model']})... (Attempt {attempt + 1}/{max_retries}){NC}")
193
+
194
+ # Make request
195
+ response = requests.post(
196
+ config["api_url"],
197
+ json=payload,
198
+ headers=headers,
199
+ timeout=30
200
+ )
201
+
202
+ if response.status_code == 200:
203
+ result = response.json()
204
+
205
+ if 'choices' in result and len(result['choices']) > 0:
206
+ raw_message = result['choices'][0]['message']['content'].strip()
207
+ # Remove markdown code blocks if AI wrapped the response
208
+ commit_message = strip_markdown_code_blocks(raw_message)
209
+ print(f"{GREEN}✓ Commit message generated successfully using {service.upper()}{NC}")
210
+ return commit_message
211
+ else:
212
+ print(f"{RED}Error: No content generated from {service.upper()} API{NC}")
213
+ if attempt < max_retries - 1:
214
+ print(f"{YELLOW}Retrying in {retry_delay} seconds...{NC}")
215
+ time.sleep(retry_delay)
216
+ continue
217
+ return None
218
+
219
+ elif response.status_code == 429:
220
+ if attempt < max_retries - 1:
221
+ wait_time = retry_delay * (2 ** attempt)
222
+ print(f"{YELLOW}⚠ Rate limit reached (429). Waiting {wait_time} seconds...{NC}")
223
+ time.sleep(wait_time)
224
+ continue
225
+ else:
226
+ print(f"{RED}✗ Error 429: Rate limit exceeded.{NC}")
227
+ return None
228
+
229
+ elif response.status_code == 404:
230
+ print(f"{RED}✗ Error 404: Model not found - {config['model']}{NC}")
231
+ print(f"{RED}Error details: {response.text}{NC}")
232
+ return None
233
+
234
+ elif response.status_code == 400:
235
+ print(f"{RED}✗ Bad Request (400): {response.text}{NC}")
236
+ return None
237
+
238
+ elif response.status_code == 401:
239
+ print(f"{RED}✗ Error 401: API key is invalid or unauthorized{NC}")
240
+ print(f"{RED}Error details: {response.text}{NC}")
241
+ return None
242
+
243
+ elif response.status_code == 403:
244
+ print(f"{RED}✗ Error 403: API key doesn't have permission{NC}")
245
+ print(f"{RED}Error details: {response.text}{NC}")
246
+ return None
247
+
248
+ else:
249
+ print(f"{RED}✗ HTTP Error {response.status_code}: {response.reason}{NC}")
250
+ print(f"{RED}Details: {response.text}{NC}")
251
+ if attempt < max_retries - 1:
252
+ print(f"{YELLOW}Retrying in {retry_delay} seconds...{NC}")
253
+ time.sleep(retry_delay)
254
+ continue
255
+ return None
256
+
257
+ except requests.exceptions.Timeout:
258
+ print(f"{RED}Error: Request timed out{NC}")
259
+ if attempt < max_retries - 1:
260
+ print(f"{YELLOW}Retrying in {retry_delay} seconds...{NC}")
261
+ time.sleep(retry_delay)
262
+ continue
263
+ return None
264
+
265
+ except requests.exceptions.ConnectionError as e:
266
+ print(f"{RED}Error: Network connection failed - {e}{NC}")
267
+ if attempt < max_retries - 1:
268
+ print(f"{YELLOW}Retrying in {retry_delay} seconds...{NC}")
269
+ time.sleep(retry_delay)
270
+ continue
271
+ return None
272
+
273
+ except json.JSONDecodeError as e:
274
+ print(f"{RED}Error: Failed to parse API response - {e}{NC}")
275
+ if attempt < max_retries - 1:
276
+ print(f"{YELLOW}Retrying in {retry_delay} seconds...{NC}")
277
+ time.sleep(retry_delay)
278
+ continue
279
+ return None
280
+
281
+ except Exception as e:
282
+ print(f"{RED}Error: Unexpected error - {e}{NC}")
283
+ if attempt < max_retries - 1:
284
+ print(f"{YELLOW}Retrying in {retry_delay} seconds...{NC}")
285
+ time.sleep(retry_delay)
286
+ continue
287
+ return None
288
+
289
+ # All retries exhausted
290
+ print(f"{RED}✗ {service.upper()} API failed after {max_retries} attempts{NC}")
291
+ return None
292
+
293
+ def test_api_connection():
294
+ """
295
+ Test selected AI service API connection
296
+ """
297
+ service, config = get_current_config()
298
+ if not config:
299
+ return False
300
+
301
+ test_prompt = "Hello, respond with 'API connection successful'"
302
+
303
+ payload = {
304
+ "model": config["model"],
305
+ "messages": [
306
+ {
307
+ "role": "user",
308
+ "content": test_prompt
309
+ }
310
+ ],
311
+ "max_tokens": 50,
312
+ "temperature": 0.7
313
+ }
314
+
315
+ headers = {
316
+ "Content-Type": "application/json",
317
+ "Authorization": f"Bearer {config['api_key']}",
318
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
319
+ }
320
+
321
+ try:
322
+ print(f"{BLUE}Testing {service.upper()} API connection with {config['model']}...{NC}")
323
+
324
+ # Make request
325
+ response = requests.post(
326
+ config["api_url"],
327
+ json=payload,
328
+ headers=headers,
329
+ timeout=10
330
+ )
331
+
332
+ if response.status_code == 200:
333
+ result = response.json()
334
+ print(f"{GREEN}✓ {service.upper()} API connection successful{NC}")
335
+
336
+ # Print response for debugging
337
+ if 'choices' in result and len(result['choices']) > 0:
338
+ text = result['choices'][0]['message']['content']
339
+ print(f"{BLUE}API Response: {text}{NC}")
340
+
341
+ return True
342
+
343
+ elif response.status_code == 429:
344
+ print(f"{RED}✗ HTTP Error 429: Rate limit reached{NC}")
345
+ print(f"{YELLOW}⚠ Wait a few minutes and try again.{NC}")
346
+ return False
347
+
348
+ elif response.status_code == 404:
349
+ print(f"{RED}✗ HTTP Error 404: Model not found - {config['model']}{NC}")
350
+ print(f"{RED}Error details: {response.text}{NC}")
351
+ return False
352
+
353
+ elif response.status_code == 400:
354
+ print(f"{RED}✗ HTTP Error 400: Bad Request{NC}")
355
+ print(f"{RED}Error details: {response.text}{NC}")
356
+ return False
357
+
358
+ elif response.status_code == 401:
359
+ print(f"{RED}✗ HTTP Error 401: API key is invalid or unauthorized{NC}")
360
+ print(f"{RED}Error details: {response.text}{NC}")
361
+ return False
362
+
363
+ elif response.status_code == 403:
364
+ print(f"{RED}✗ HTTP Error 403: API key doesn't have permission{NC}")
365
+ print(f"{RED}Error details: {response.text}{NC}")
366
+ return False
367
+
368
+ else:
369
+ print(f"{RED}✗ {service.upper()} API connection failed with status {response.status_code}{NC}")
370
+ print(f"{RED}Response: {response.text}{NC}")
371
+ return False
372
+
373
+ except requests.exceptions.Timeout:
374
+ print(f"{RED}✗ Request timed out{NC}")
375
+ return False
376
+
377
+ except requests.exceptions.ConnectionError as e:
378
+ print(f"{RED}✗ Network Error: {e}{NC}")
379
+ return False
380
+
381
+ except Exception as e:
382
+ print(f"{RED}✗ {service.upper()} API connection failed: {e}{NC}")
383
+ return False
384
+
385
+ def main():
386
+ # Test the API connection
387
+ DEFAULT_AI_SERVICE = get_default_ai_service()
388
+ print(f"{BLUE}{'='*50}{NC}")
389
+ print(f"{BLUE}{DEFAULT_AI_SERVICE.upper()} API Connection Test{NC}")
390
+ print(f"{BLUE}{'='*50}{NC}")
391
+ return 0 if test_api_connection() else 1
392
+
393
+
394
+ if __name__ == "__main__":
395
+ sys.exit(main())
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import subprocess
4
+ import os
5
+
6
+ # Import common utilities
7
+ from common_utils import open_file_with_default_app, BLUE, NC
8
+
9
+ def main():
10
+ # Use current working directory instead of hardcoded path
11
+ folder_path = os.getcwd()
12
+
13
+ print(f"{BLUE}Working in directory: {folder_path}{NC}")
14
+
15
+ # Change the working directory to the specified folder (already current directory)
16
+ os.chdir(folder_path)
17
+
18
+ # Execute the git diff command and write output to diff_output.txt
19
+ with open('diff_output.txt', 'w') as file:
20
+ subprocess.run(['git', 'diff'], stdout=file)
21
+
22
+ # Text to append
23
+ text_to_append = '\n================\n\nhere is my changes...give me git push msg and description of changes with using of proper emoticons...dont give any long boring texts, "STRICTLY i dont need any explanations"\n'
24
+
25
+ # Append the specified text to diff_output.txt
26
+ with open('diff_output.txt', 'a') as file:
27
+ file.write(text_to_append)
28
+
29
+ # Open the file with default editor
30
+ open_file_with_default_app('diff_output.txt')
31
+
32
+
33
+ if __name__ == "__main__":
34
+ main()