flutter-dev 0.1.9__tar.gz → 0.1.11__tar.gz
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.
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/.env.example +8 -9
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/CHANGELOG.md +33 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/PKG-INFO +42 -16
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/README.md +41 -15
- flutter_dev-0.1.11/core/ai_config.py +297 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/fdev.py +17 -8
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/flutter_dev.egg-info/SOURCES.txt +3 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/gemini_api.py +19 -74
- flutter_dev-0.1.11/managers/ai.py +232 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/managers/app.py +42 -25
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/managers/doctor.py +44 -31
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/pyproject.toml +1 -1
- flutter_dev-0.1.11/tests/test_ai_config.py +187 -0
- flutter_dev-0.1.11/tests/test_app_commands.py +66 -0
- flutter_dev-0.1.9/managers/ai.py +0 -113
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/LICENSE +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/MANIFEST.in +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/common_utils.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/core/__init__.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/core/constants.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/core/state.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/create_page.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/git_diff_output_editor.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/install_legacy.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/managers/__init__.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/managers/brew.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/managers/build.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/managers/datetime.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/managers/device.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/managers/git.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/managers/git_account.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/managers/localization.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/managers/merge.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/managers/mirror.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/managers/package.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/managers/project.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/managers/web_deploy.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/requirements.txt +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/setup.cfg +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/setup.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/switch_ai.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/tests/test_fdev_cli.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/tests/test_localization.py +0 -0
- {flutter_dev-0.1.9 → flutter_dev-0.1.11}/tests/test_package_commands.py +0 -0
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
# AI API Keys Configuration
|
|
2
|
-
#
|
|
3
|
-
# এই file টি .gitignore এ add করতে হবে
|
|
2
|
+
# API keys secure রাখুন এবং কারও সঙ্গে share করবেন না
|
|
4
3
|
|
|
5
|
-
# Groq API
|
|
6
|
-
GROQ_API_KEY=
|
|
4
|
+
# Groq API
|
|
5
|
+
GROQ_API_KEY=
|
|
7
6
|
GROQ_API_URL=https://api.groq.com/openai/v1/chat/completions
|
|
8
7
|
GROQ_MODEL=llama-3.3-70b-versatile
|
|
9
8
|
|
|
10
9
|
# Mistral AI API
|
|
11
|
-
MISTRAL_API_KEY=
|
|
10
|
+
MISTRAL_API_KEY=
|
|
12
11
|
MISTRAL_API_URL=https://api.mistral.ai/v1/chat/completions
|
|
13
|
-
MISTRAL_MODEL=mistral-medium
|
|
12
|
+
MISTRAL_MODEL=mistral-medium-latest
|
|
14
13
|
|
|
15
14
|
# SambaNova API
|
|
16
|
-
SAMBANOVA_API_KEY=
|
|
15
|
+
SAMBANOVA_API_KEY=
|
|
17
16
|
SAMBANOVA_API_URL=https://api.sambanova.ai/v1/chat/completions
|
|
18
17
|
SAMBANOVA_MODEL=Meta-Llama-3.3-70B-Instruct
|
|
19
18
|
|
|
20
19
|
# OpenRouter API
|
|
21
|
-
OPENROUTER_API_KEY=
|
|
20
|
+
OPENROUTER_API_KEY=
|
|
22
21
|
OPENROUTER_API_URL=https://openrouter.ai/api/v1/chat/completions
|
|
23
22
|
OPENROUTER_MODEL=openai/gpt-3.5-turbo
|
|
24
23
|
|
|
25
24
|
# Default AI Service (groq, mistral, sambanova, openrouter)
|
|
26
|
-
DEFAULT_AI_SERVICE=groq
|
|
25
|
+
DEFAULT_AI_SERVICE=groq
|
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.1.11 - 2026-07-22
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added Python regression coverage for project-root and outside-project `fdev clear-data` behavior.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Changed `fdev clear-data` to target the current project's Android package without confirmation when run from a Flutter project root, matching `fdev uninstall`; outside a project it still confirms before clearing the foreground app.
|
|
14
|
+
- Updated the PyPI publishing workflow to run on pushes to the `deployment` branch as well as manual dispatches.
|
|
15
|
+
|
|
16
|
+
## 0.1.10 - 2026-07-16
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Added `fdev init` to create or repair persistent AI configuration with safe provider defaults and empty API-key fields.
|
|
21
|
+
- Added interactive hidden API-key entry and default/custom model selection when AI configuration is incomplete.
|
|
22
|
+
- Added `fdev ai <provider> <model>` for changing a provider's model without replacing its API key.
|
|
23
|
+
- Added Python coverage for configuration precedence, dotenv parsing, atomic updates, cancellation, permissions, platform paths, and doctor status.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Unified `fdev ai`, `fdev doctor`, `gemini-api`, and `fdev commit` around the same persistent AI configuration resolver.
|
|
28
|
+
- Updated the default Mistral model to `mistral-medium-latest`.
|
|
29
|
+
- Limited build-output directory creation to commands that use those directories.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Removed credentials from `.env.example` and replaced them with empty API-key values.
|
|
34
|
+
- Made AI configuration writes atomic and restricted the generated file to the current user on Unix-like systems.
|
|
35
|
+
- Cancelled AI setup without saving a partial key or sending a request when interrupted during key/model entry.
|
|
36
|
+
- Prevented exported provider/model overrides from being reported as successfully replaced by file-only changes.
|
|
37
|
+
|
|
5
38
|
## 0.1.9 - 2026-07-14
|
|
6
39
|
|
|
7
40
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flutter-dev
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.11
|
|
4
4
|
Summary: A cross-platform CLI toolkit for Flutter development workflows.
|
|
5
5
|
Author: Royal Court BD
|
|
6
6
|
License-Expression: MIT
|
|
@@ -42,14 +42,14 @@ The package is published on PyPI as `flutter-dev` and installs console commands
|
|
|
42
42
|
python -m pip install flutter-dev
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
Current package metadata declares version `0.1.
|
|
45
|
+
Current package metadata declares version `0.1.11`, Python `>=3.8`, MIT license, and support for macOS, Linux, and Windows.
|
|
46
46
|
|
|
47
47
|
## Key Features
|
|
48
48
|
|
|
49
49
|
- Flutter build automation for APK, split APK, AAB, IPA, release install, and Firebase web deploy.
|
|
50
50
|
- Project maintenance helpers for `flutter clean`, dependency refresh, `build_runner`, localization generation, analysis, formatting, cache repair, and CocoaPods.
|
|
51
51
|
- Localization CSV helpers for exporting ARB files to translator-friendly CSV files and generating ARB files back from CSV.
|
|
52
|
-
- Android device helpers for APK install, app uninstall, foreground app data clearing, screen mirroring with `scrcpy`, wireless ADB setup, and Date & Time settings.
|
|
52
|
+
- Android device helpers for APK install, app uninstall, project/foreground app data clearing, screen mirroring with `scrcpy`, wireless ADB setup, and Date & Time settings.
|
|
53
53
|
- AI-assisted Git commits using Groq, Mistral, SambaNova, or OpenRouter-compatible chat completion APIs.
|
|
54
54
|
- Git workflow helpers for version tags, branch sync, deployment branch merge, discard, and GitHub account management through `gh`.
|
|
55
55
|
- Feature/page generator that creates a `lib/features/<name>` structure using the project's presenter/base-state pattern.
|
|
@@ -137,6 +137,9 @@ fdev upgrade
|
|
|
137
137
|
# Check environment and optional tools
|
|
138
138
|
fdev doctor
|
|
139
139
|
|
|
140
|
+
# Initialize persistent AI configuration
|
|
141
|
+
fdev init
|
|
142
|
+
|
|
140
143
|
# Generate localization files
|
|
141
144
|
fdev lang
|
|
142
145
|
|
|
@@ -198,7 +201,7 @@ Android APK/AAB build commands rename output files using the Android app label a
|
|
|
198
201
|
| --- | --- |
|
|
199
202
|
| `fdev install` | Installs a built APK from `build/app/outputs/flutter-apk`, prefers `arm64-v8a`, retries after uninstall on failure, then launches the app. |
|
|
200
203
|
| `fdev uninstall` | In a Flutter project root, uninstalls by package/bundle id. Outside a project, detects the foreground app and asks for confirmation. |
|
|
201
|
-
| `fdev clear-data` |
|
|
204
|
+
| `fdev clear-data` | In a Flutter project root, clears the project's Android package data without confirmation. Outside a project, detects the foreground app and shows a default-Yes safety prompt. iOS prints manual options. |
|
|
202
205
|
| `fdev install-scrcpy` | Installs `scrcpy` using the best supported package manager for the current platform. |
|
|
203
206
|
| `fdev mirror` | Launches `scrcpy` for a selected Android device. |
|
|
204
207
|
| `fdev mirror --wireless` | Guides wireless ADB setup using a USB-connected Android device. |
|
|
@@ -239,10 +242,12 @@ Switching GitHub accounts runs `gh auth switch` and `gh auth setup-git`, but it
|
|
|
239
242
|
| `fdev` | Shows command help. |
|
|
240
243
|
| `fdev help` | Shows command help. |
|
|
241
244
|
| `fdev doctor` | Checks platform, Python, Flutter/Dart/Git, Python packages, Android/iOS tools, optional CLIs, `.env`, Flutter project detection, and global `fdev` availability. |
|
|
245
|
+
| `fdev init` | Creates or repairs the persistent AI `.env` with safe provider defaults and empty API keys. |
|
|
242
246
|
| `fdev upgrade` | Shows the current version, Python environment, package location, and PyPI source, then upgrades with `python -m pip install --upgrade flutter-dev` and reports the resulting version. |
|
|
243
247
|
| `fdev delete` | Detects the OS, asks for confirmation, then uninstalls the active Python environment's `flutter-dev` package. Use `fdev delete --yes` to skip confirmation. |
|
|
244
248
|
| `fdev ai` | Shows the current AI provider and opens an interactive provider switch prompt. |
|
|
245
249
|
| `fdev ai <provider>` | Switches `DEFAULT_AI_SERVICE` to `groq`, `mistral`, `sambanova`, or `openrouter`. |
|
|
250
|
+
| `fdev ai <provider> <model>` | Switches provider when needed and saves a custom model ID for that provider. |
|
|
246
251
|
| `fdev brew` | Interactive Homebrew package manager for viewing packages, uninstalling with cleanup, clearing cache, and global cleanup. |
|
|
247
252
|
| `fdev merge` | Reads paths from `paths.txt`, merges file contents into `path_merge_files.txt`, and opens the result when there are no failures. |
|
|
248
253
|
| `gemini-api` | Tests the currently selected AI provider configuration. |
|
|
@@ -266,7 +271,7 @@ GROQ_MODEL=llama-3.3-70b-versatile
|
|
|
266
271
|
|
|
267
272
|
MISTRAL_API_KEY=your_mistral_api_key_here
|
|
268
273
|
MISTRAL_API_URL=https://api.mistral.ai/v1/chat/completions
|
|
269
|
-
MISTRAL_MODEL=mistral-medium
|
|
274
|
+
MISTRAL_MODEL=mistral-medium-latest
|
|
270
275
|
|
|
271
276
|
SAMBANOVA_API_KEY=your_sambanova_api_key_here
|
|
272
277
|
SAMBANOVA_API_URL=https://api.sambanova.ai/v1/chat/completions
|
|
@@ -283,26 +288,47 @@ The AI runtime searches for configuration in this order:
|
|
|
283
288
|
|
|
284
289
|
1. `FLUTTER_DEV_ENV`
|
|
285
290
|
2. `$XDG_CONFIG_HOME/flutter-dev/.env`
|
|
286
|
-
3. `~/.config/flutter-dev/.env`
|
|
287
|
-
4.
|
|
288
|
-
5.
|
|
289
|
-
6. `.env` in the current working directory
|
|
291
|
+
3. `%APPDATA%/flutter-dev/.env` on Windows, otherwise `~/.config/flutter-dev/.env`
|
|
292
|
+
4. The legacy `~/.config/flutter-dev/.env` path on Windows
|
|
293
|
+
5. `~/scripts/flutter-tools/.env`
|
|
290
294
|
|
|
291
295
|
Recommended setup:
|
|
292
296
|
|
|
293
297
|
```bash
|
|
294
|
-
|
|
295
|
-
nano ~/.config/flutter-dev/.env
|
|
298
|
+
fdev init
|
|
296
299
|
```
|
|
297
300
|
|
|
298
|
-
|
|
301
|
+
`fdev init` never writes real credentials. It creates all provider URL/model
|
|
302
|
+
defaults with empty API-key fields, preserves existing values, and restricts the
|
|
303
|
+
file to the current user on Unix-like systems. On macOS and Linux the default
|
|
304
|
+
path is `~/.config/flutter-dev/.env`.
|
|
305
|
+
|
|
306
|
+
Then select a provider and test:
|
|
299
307
|
|
|
300
308
|
```bash
|
|
301
|
-
fdev ai
|
|
309
|
+
fdev ai mistral
|
|
302
310
|
gemini-api
|
|
303
311
|
```
|
|
304
312
|
|
|
305
|
-
|
|
313
|
+
If the selected provider's key is missing, `fdev commit` or `gemini-api` asks
|
|
314
|
+
for it using hidden terminal input and saves it to the active configuration.
|
|
315
|
+
After a new key is entered, press Enter to use the displayed default model or
|
|
316
|
+
enter another model ID for the same provider. Exported environment variables
|
|
317
|
+
continue to take precedence over values stored in `.env`.
|
|
318
|
+
|
|
319
|
+
Pressing `Ctrl+C` during key/model setup cancels the operation without saving a
|
|
320
|
+
partial key or sending an AI request. If an exported variable conflicts with a
|
|
321
|
+
provider/model change, `fdev` reports the override and asks you to unset it
|
|
322
|
+
instead of claiming that the file change became active.
|
|
323
|
+
|
|
324
|
+
Change the model later without replacing the API key:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
fdev ai groq llama-3.1-8b-instant
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
`fdev doctor`, `fdev ai`, `gemini-api`, and `fdev commit` use the same persistent
|
|
331
|
+
configuration lookup.
|
|
306
332
|
|
|
307
333
|
## Feature/Page Generation
|
|
308
334
|
|
|
@@ -364,7 +390,7 @@ core/constants.py Shared regex patterns, paths, and build command constant
|
|
|
364
390
|
core/state.py Selected Android device state
|
|
365
391
|
managers/build.py APK/AAB/IPA/release-run build workflow
|
|
366
392
|
managers/project.py setup, cleanup, lang, db, pod, cache repair
|
|
367
|
-
managers/app.py install, uninstall, foreground app data clearing
|
|
393
|
+
managers/app.py install, uninstall, project/foreground app data clearing
|
|
368
394
|
managers/device.py ADB device detection and selection
|
|
369
395
|
managers/mirror.py scrcpy install, mirror, wireless ADB setup
|
|
370
396
|
managers/git.py commit, tag, discard, branch sync, deployment merge
|
|
@@ -387,7 +413,7 @@ The installed Python layout is top-level modules plus the `core` and `managers`
|
|
|
387
413
|
- `fdev tag` updates `pubspec.yaml`, creates a commit, creates a Git tag, and pushes both.
|
|
388
414
|
- `fdev discard` removes tracked changes and untracked files after confirmation.
|
|
389
415
|
- `fdev cleanup` applies `dart fix`, formats the project, and runs a major-version dependency upgrade.
|
|
390
|
-
- `fdev clear-data`
|
|
416
|
+
- `fdev clear-data` targets the current project's Android package without confirmation when run from a Flutter project root. Outside a project, it targets the foreground app after a default-Yes `[Y/n]` confirmation.
|
|
391
417
|
- `fdev uninstall` may fall back to foreground app detection outside a Flutter project root.
|
|
392
418
|
- `fdev upgrade` and `fdev delete` manage the local `flutter-dev` Python package, not the Flutter app on a device.
|
|
393
419
|
- `fdev upgrade` reports the installed version and location before upgrading, then shows the before→after version when the active process can verify it immediately.
|
|
@@ -8,14 +8,14 @@ The package is published on PyPI as `flutter-dev` and installs console commands
|
|
|
8
8
|
python -m pip install flutter-dev
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Current package metadata declares version `0.1.
|
|
11
|
+
Current package metadata declares version `0.1.11`, Python `>=3.8`, MIT license, and support for macOS, Linux, and Windows.
|
|
12
12
|
|
|
13
13
|
## Key Features
|
|
14
14
|
|
|
15
15
|
- Flutter build automation for APK, split APK, AAB, IPA, release install, and Firebase web deploy.
|
|
16
16
|
- Project maintenance helpers for `flutter clean`, dependency refresh, `build_runner`, localization generation, analysis, formatting, cache repair, and CocoaPods.
|
|
17
17
|
- Localization CSV helpers for exporting ARB files to translator-friendly CSV files and generating ARB files back from CSV.
|
|
18
|
-
- Android device helpers for APK install, app uninstall, foreground app data clearing, screen mirroring with `scrcpy`, wireless ADB setup, and Date & Time settings.
|
|
18
|
+
- Android device helpers for APK install, app uninstall, project/foreground app data clearing, screen mirroring with `scrcpy`, wireless ADB setup, and Date & Time settings.
|
|
19
19
|
- AI-assisted Git commits using Groq, Mistral, SambaNova, or OpenRouter-compatible chat completion APIs.
|
|
20
20
|
- Git workflow helpers for version tags, branch sync, deployment branch merge, discard, and GitHub account management through `gh`.
|
|
21
21
|
- Feature/page generator that creates a `lib/features/<name>` structure using the project's presenter/base-state pattern.
|
|
@@ -103,6 +103,9 @@ fdev upgrade
|
|
|
103
103
|
# Check environment and optional tools
|
|
104
104
|
fdev doctor
|
|
105
105
|
|
|
106
|
+
# Initialize persistent AI configuration
|
|
107
|
+
fdev init
|
|
108
|
+
|
|
106
109
|
# Generate localization files
|
|
107
110
|
fdev lang
|
|
108
111
|
|
|
@@ -164,7 +167,7 @@ Android APK/AAB build commands rename output files using the Android app label a
|
|
|
164
167
|
| --- | --- |
|
|
165
168
|
| `fdev install` | Installs a built APK from `build/app/outputs/flutter-apk`, prefers `arm64-v8a`, retries after uninstall on failure, then launches the app. |
|
|
166
169
|
| `fdev uninstall` | In a Flutter project root, uninstalls by package/bundle id. Outside a project, detects the foreground app and asks for confirmation. |
|
|
167
|
-
| `fdev clear-data` |
|
|
170
|
+
| `fdev clear-data` | In a Flutter project root, clears the project's Android package data without confirmation. Outside a project, detects the foreground app and shows a default-Yes safety prompt. iOS prints manual options. |
|
|
168
171
|
| `fdev install-scrcpy` | Installs `scrcpy` using the best supported package manager for the current platform. |
|
|
169
172
|
| `fdev mirror` | Launches `scrcpy` for a selected Android device. |
|
|
170
173
|
| `fdev mirror --wireless` | Guides wireless ADB setup using a USB-connected Android device. |
|
|
@@ -205,10 +208,12 @@ Switching GitHub accounts runs `gh auth switch` and `gh auth setup-git`, but it
|
|
|
205
208
|
| `fdev` | Shows command help. |
|
|
206
209
|
| `fdev help` | Shows command help. |
|
|
207
210
|
| `fdev doctor` | Checks platform, Python, Flutter/Dart/Git, Python packages, Android/iOS tools, optional CLIs, `.env`, Flutter project detection, and global `fdev` availability. |
|
|
211
|
+
| `fdev init` | Creates or repairs the persistent AI `.env` with safe provider defaults and empty API keys. |
|
|
208
212
|
| `fdev upgrade` | Shows the current version, Python environment, package location, and PyPI source, then upgrades with `python -m pip install --upgrade flutter-dev` and reports the resulting version. |
|
|
209
213
|
| `fdev delete` | Detects the OS, asks for confirmation, then uninstalls the active Python environment's `flutter-dev` package. Use `fdev delete --yes` to skip confirmation. |
|
|
210
214
|
| `fdev ai` | Shows the current AI provider and opens an interactive provider switch prompt. |
|
|
211
215
|
| `fdev ai <provider>` | Switches `DEFAULT_AI_SERVICE` to `groq`, `mistral`, `sambanova`, or `openrouter`. |
|
|
216
|
+
| `fdev ai <provider> <model>` | Switches provider when needed and saves a custom model ID for that provider. |
|
|
212
217
|
| `fdev brew` | Interactive Homebrew package manager for viewing packages, uninstalling with cleanup, clearing cache, and global cleanup. |
|
|
213
218
|
| `fdev merge` | Reads paths from `paths.txt`, merges file contents into `path_merge_files.txt`, and opens the result when there are no failures. |
|
|
214
219
|
| `gemini-api` | Tests the currently selected AI provider configuration. |
|
|
@@ -232,7 +237,7 @@ GROQ_MODEL=llama-3.3-70b-versatile
|
|
|
232
237
|
|
|
233
238
|
MISTRAL_API_KEY=your_mistral_api_key_here
|
|
234
239
|
MISTRAL_API_URL=https://api.mistral.ai/v1/chat/completions
|
|
235
|
-
MISTRAL_MODEL=mistral-medium
|
|
240
|
+
MISTRAL_MODEL=mistral-medium-latest
|
|
236
241
|
|
|
237
242
|
SAMBANOVA_API_KEY=your_sambanova_api_key_here
|
|
238
243
|
SAMBANOVA_API_URL=https://api.sambanova.ai/v1/chat/completions
|
|
@@ -249,26 +254,47 @@ The AI runtime searches for configuration in this order:
|
|
|
249
254
|
|
|
250
255
|
1. `FLUTTER_DEV_ENV`
|
|
251
256
|
2. `$XDG_CONFIG_HOME/flutter-dev/.env`
|
|
252
|
-
3. `~/.config/flutter-dev/.env`
|
|
253
|
-
4.
|
|
254
|
-
5.
|
|
255
|
-
6. `.env` in the current working directory
|
|
257
|
+
3. `%APPDATA%/flutter-dev/.env` on Windows, otherwise `~/.config/flutter-dev/.env`
|
|
258
|
+
4. The legacy `~/.config/flutter-dev/.env` path on Windows
|
|
259
|
+
5. `~/scripts/flutter-tools/.env`
|
|
256
260
|
|
|
257
261
|
Recommended setup:
|
|
258
262
|
|
|
259
263
|
```bash
|
|
260
|
-
|
|
261
|
-
nano ~/.config/flutter-dev/.env
|
|
264
|
+
fdev init
|
|
262
265
|
```
|
|
263
266
|
|
|
264
|
-
|
|
267
|
+
`fdev init` never writes real credentials. It creates all provider URL/model
|
|
268
|
+
defaults with empty API-key fields, preserves existing values, and restricts the
|
|
269
|
+
file to the current user on Unix-like systems. On macOS and Linux the default
|
|
270
|
+
path is `~/.config/flutter-dev/.env`.
|
|
271
|
+
|
|
272
|
+
Then select a provider and test:
|
|
265
273
|
|
|
266
274
|
```bash
|
|
267
|
-
fdev ai
|
|
275
|
+
fdev ai mistral
|
|
268
276
|
gemini-api
|
|
269
277
|
```
|
|
270
278
|
|
|
271
|
-
|
|
279
|
+
If the selected provider's key is missing, `fdev commit` or `gemini-api` asks
|
|
280
|
+
for it using hidden terminal input and saves it to the active configuration.
|
|
281
|
+
After a new key is entered, press Enter to use the displayed default model or
|
|
282
|
+
enter another model ID for the same provider. Exported environment variables
|
|
283
|
+
continue to take precedence over values stored in `.env`.
|
|
284
|
+
|
|
285
|
+
Pressing `Ctrl+C` during key/model setup cancels the operation without saving a
|
|
286
|
+
partial key or sending an AI request. If an exported variable conflicts with a
|
|
287
|
+
provider/model change, `fdev` reports the override and asks you to unset it
|
|
288
|
+
instead of claiming that the file change became active.
|
|
289
|
+
|
|
290
|
+
Change the model later without replacing the API key:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
fdev ai groq llama-3.1-8b-instant
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`fdev doctor`, `fdev ai`, `gemini-api`, and `fdev commit` use the same persistent
|
|
297
|
+
configuration lookup.
|
|
272
298
|
|
|
273
299
|
## Feature/Page Generation
|
|
274
300
|
|
|
@@ -330,7 +356,7 @@ core/constants.py Shared regex patterns, paths, and build command constant
|
|
|
330
356
|
core/state.py Selected Android device state
|
|
331
357
|
managers/build.py APK/AAB/IPA/release-run build workflow
|
|
332
358
|
managers/project.py setup, cleanup, lang, db, pod, cache repair
|
|
333
|
-
managers/app.py install, uninstall, foreground app data clearing
|
|
359
|
+
managers/app.py install, uninstall, project/foreground app data clearing
|
|
334
360
|
managers/device.py ADB device detection and selection
|
|
335
361
|
managers/mirror.py scrcpy install, mirror, wireless ADB setup
|
|
336
362
|
managers/git.py commit, tag, discard, branch sync, deployment merge
|
|
@@ -353,7 +379,7 @@ The installed Python layout is top-level modules plus the `core` and `managers`
|
|
|
353
379
|
- `fdev tag` updates `pubspec.yaml`, creates a commit, creates a Git tag, and pushes both.
|
|
354
380
|
- `fdev discard` removes tracked changes and untracked files after confirmation.
|
|
355
381
|
- `fdev cleanup` applies `dart fix`, formats the project, and runs a major-version dependency upgrade.
|
|
356
|
-
- `fdev clear-data`
|
|
382
|
+
- `fdev clear-data` targets the current project's Android package without confirmation when run from a Flutter project root. Outside a project, it targets the foreground app after a default-Yes `[Y/n]` confirmation.
|
|
357
383
|
- `fdev uninstall` may fall back to foreground app detection outside a Flutter project root.
|
|
358
384
|
- `fdev upgrade` and `fdev delete` manage the local `flutter-dev` Python package, not the Flutter app on a device.
|
|
359
385
|
- `fdev upgrade` reports the installed version and location before upgrading, then shows the before→after version when the active process can verify it immediately.
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Shared AI configuration paths, defaults, and persistence helpers."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import re
|
|
6
|
+
import tempfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from dotenv import dotenv_values
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
VALID_AI_SERVICES = ("groq", "mistral", "sambanova", "openrouter")
|
|
13
|
+
|
|
14
|
+
AI_SERVICE_DEFAULTS = {
|
|
15
|
+
"groq": {
|
|
16
|
+
"api_key_env": "GROQ_API_KEY",
|
|
17
|
+
"api_url_env": "GROQ_API_URL",
|
|
18
|
+
"model_env": "GROQ_MODEL",
|
|
19
|
+
"api_url": "https://api.groq.com/openai/v1/chat/completions",
|
|
20
|
+
"model": "llama-3.3-70b-versatile",
|
|
21
|
+
},
|
|
22
|
+
"mistral": {
|
|
23
|
+
"api_key_env": "MISTRAL_API_KEY",
|
|
24
|
+
"api_url_env": "MISTRAL_API_URL",
|
|
25
|
+
"model_env": "MISTRAL_MODEL",
|
|
26
|
+
"api_url": "https://api.mistral.ai/v1/chat/completions",
|
|
27
|
+
"model": "mistral-medium-latest",
|
|
28
|
+
},
|
|
29
|
+
"sambanova": {
|
|
30
|
+
"api_key_env": "SAMBANOVA_API_KEY",
|
|
31
|
+
"api_url_env": "SAMBANOVA_API_URL",
|
|
32
|
+
"model_env": "SAMBANOVA_MODEL",
|
|
33
|
+
"api_url": "https://api.sambanova.ai/v1/chat/completions",
|
|
34
|
+
"model": "Meta-Llama-3.3-70B-Instruct",
|
|
35
|
+
},
|
|
36
|
+
"openrouter": {
|
|
37
|
+
"api_key_env": "OPENROUTER_API_KEY",
|
|
38
|
+
"api_url_env": "OPENROUTER_API_URL",
|
|
39
|
+
"model_env": "OPENROUTER_MODEL",
|
|
40
|
+
"api_url": "https://openrouter.ai/api/v1/chat/completions",
|
|
41
|
+
"model": "openai/gpt-3.5-turbo",
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_preferred_env_path():
|
|
47
|
+
"""Return the user config path where fdev should create its .env file."""
|
|
48
|
+
explicit_path = os.getenv("FLUTTER_DEV_ENV")
|
|
49
|
+
if explicit_path:
|
|
50
|
+
return Path(explicit_path).expanduser()
|
|
51
|
+
|
|
52
|
+
xdg_config_home = os.getenv("XDG_CONFIG_HOME")
|
|
53
|
+
if xdg_config_home:
|
|
54
|
+
return Path(xdg_config_home).expanduser() / "flutter-dev" / ".env"
|
|
55
|
+
|
|
56
|
+
if platform.system() == "Windows" and os.getenv("APPDATA"):
|
|
57
|
+
return Path(os.environ["APPDATA"]).expanduser() / "flutter-dev" / ".env"
|
|
58
|
+
|
|
59
|
+
# Keep the established path on macOS and Linux for backward compatibility.
|
|
60
|
+
return Path.home() / ".config" / "flutter-dev" / ".env"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_persistent_env_candidates():
|
|
64
|
+
"""Return user-owned config candidates without project-local fallbacks."""
|
|
65
|
+
preferred_path = get_preferred_env_path()
|
|
66
|
+
candidates = [
|
|
67
|
+
preferred_path,
|
|
68
|
+
Path.home() / ".config" / "flutter-dev" / ".env",
|
|
69
|
+
Path.home() / "scripts" / "flutter-tools" / ".env",
|
|
70
|
+
]
|
|
71
|
+
unique_candidates = []
|
|
72
|
+
seen = set()
|
|
73
|
+
for candidate in candidates:
|
|
74
|
+
normalized = str(candidate.expanduser())
|
|
75
|
+
if normalized not in seen:
|
|
76
|
+
seen.add(normalized)
|
|
77
|
+
unique_candidates.append(candidate.expanduser())
|
|
78
|
+
return unique_candidates
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def find_persistent_env_file():
|
|
82
|
+
"""Return an existing user-owned config file, or None."""
|
|
83
|
+
# Explicit path controls must not silently fall back to another file.
|
|
84
|
+
if os.getenv("FLUTTER_DEV_ENV") or os.getenv("XDG_CONFIG_HOME"):
|
|
85
|
+
preferred_path = get_preferred_env_path()
|
|
86
|
+
return preferred_path if preferred_path.is_file() else None
|
|
87
|
+
|
|
88
|
+
for candidate in get_persistent_env_candidates():
|
|
89
|
+
if candidate.is_file():
|
|
90
|
+
return candidate
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def get_active_env_path():
|
|
95
|
+
"""Return the existing runtime config, or the preferred creation path."""
|
|
96
|
+
return find_persistent_env_file() or get_preferred_env_path()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def build_env_template(default_service="groq"):
|
|
100
|
+
"""Build a safe config template containing no API credentials."""
|
|
101
|
+
return f"""# AI API Keys Configuration
|
|
102
|
+
# API keys secure রাখুন এবং কারও সঙ্গে share করবেন না
|
|
103
|
+
|
|
104
|
+
# Groq API
|
|
105
|
+
GROQ_API_KEY=
|
|
106
|
+
GROQ_API_URL={AI_SERVICE_DEFAULTS['groq']['api_url']}
|
|
107
|
+
GROQ_MODEL={AI_SERVICE_DEFAULTS['groq']['model']}
|
|
108
|
+
|
|
109
|
+
# Mistral AI API
|
|
110
|
+
MISTRAL_API_KEY=
|
|
111
|
+
MISTRAL_API_URL={AI_SERVICE_DEFAULTS['mistral']['api_url']}
|
|
112
|
+
MISTRAL_MODEL={AI_SERVICE_DEFAULTS['mistral']['model']}
|
|
113
|
+
|
|
114
|
+
# SambaNova API
|
|
115
|
+
SAMBANOVA_API_KEY=
|
|
116
|
+
SAMBANOVA_API_URL={AI_SERVICE_DEFAULTS['sambanova']['api_url']}
|
|
117
|
+
SAMBANOVA_MODEL={AI_SERVICE_DEFAULTS['sambanova']['model']}
|
|
118
|
+
|
|
119
|
+
# OpenRouter API
|
|
120
|
+
OPENROUTER_API_KEY=
|
|
121
|
+
OPENROUTER_API_URL={AI_SERVICE_DEFAULTS['openrouter']['api_url']}
|
|
122
|
+
OPENROUTER_MODEL={AI_SERVICE_DEFAULTS['openrouter']['model']}
|
|
123
|
+
|
|
124
|
+
# Default AI Service (groq, mistral, sambanova, openrouter)
|
|
125
|
+
DEFAULT_AI_SERVICE={default_service}
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def read_env_file(env_path):
|
|
130
|
+
"""Read an env file with the same parser used by the AI runtime."""
|
|
131
|
+
env_path = Path(env_path)
|
|
132
|
+
if not env_path.is_file():
|
|
133
|
+
return {}
|
|
134
|
+
|
|
135
|
+
parsed = dotenv_values(env_path, interpolate=False, encoding="utf-8")
|
|
136
|
+
return {key: value or "" for key, value in parsed.items()}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def resolve_ai_config(file_values, environ=None):
|
|
140
|
+
"""Resolve one serializable AI configuration from file and process data."""
|
|
141
|
+
process_values = os.environ if environ is None else environ
|
|
142
|
+
|
|
143
|
+
def effective_value(key, fallback=""):
|
|
144
|
+
if key in process_values and process_values[key]:
|
|
145
|
+
return process_values[key]
|
|
146
|
+
return file_values.get(key, fallback)
|
|
147
|
+
|
|
148
|
+
providers = {}
|
|
149
|
+
relevant_environment_keys = {"DEFAULT_AI_SERVICE"}
|
|
150
|
+
for service in VALID_AI_SERVICES:
|
|
151
|
+
defaults = AI_SERVICE_DEFAULTS[service]
|
|
152
|
+
relevant_environment_keys.update(
|
|
153
|
+
{
|
|
154
|
+
defaults["api_key_env"],
|
|
155
|
+
defaults["api_url_env"],
|
|
156
|
+
defaults["model_env"],
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
providers[service] = {
|
|
160
|
+
"api_key": effective_value(defaults["api_key_env"]),
|
|
161
|
+
"api_url": effective_value(
|
|
162
|
+
defaults["api_url_env"], defaults["api_url"]
|
|
163
|
+
),
|
|
164
|
+
"model": effective_value(defaults["model_env"], defaults["model"]),
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
service = effective_value("DEFAULT_AI_SERVICE", "groq").strip().lower()
|
|
168
|
+
return {
|
|
169
|
+
"service": service,
|
|
170
|
+
"providers": providers,
|
|
171
|
+
"environment_overrides": sorted(
|
|
172
|
+
key
|
|
173
|
+
for key in relevant_environment_keys
|
|
174
|
+
if process_values.get(key)
|
|
175
|
+
),
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def load_resolved_ai_config(env_path, environ=None):
|
|
180
|
+
"""Load and resolve configuration while retaining its source path."""
|
|
181
|
+
resolved = resolve_ai_config(read_env_file(env_path), environ=environ)
|
|
182
|
+
resolved["env_path"] = str(Path(env_path))
|
|
183
|
+
return resolved
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def update_env_file_value(env_path, key, value):
|
|
187
|
+
"""Update one env value atomically."""
|
|
188
|
+
update_env_file_values(env_path, {key: value})
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def update_env_file_values(env_path, updates):
|
|
192
|
+
"""Update env values in one atomic write, removing duplicate target keys."""
|
|
193
|
+
normalized_updates = {}
|
|
194
|
+
for key, value in updates.items():
|
|
195
|
+
value = str(value)
|
|
196
|
+
if "\n" in value or "\r" in value:
|
|
197
|
+
raise ValueError(f"Invalid newline in {key}")
|
|
198
|
+
normalized_updates[key] = value
|
|
199
|
+
|
|
200
|
+
env_path = Path(env_path)
|
|
201
|
+
env_path.parent.mkdir(parents=True, exist_ok=True)
|
|
202
|
+
content = env_path.read_text(encoding="utf-8") if env_path.exists() else ""
|
|
203
|
+
lines = content.splitlines()
|
|
204
|
+
output_lines = []
|
|
205
|
+
written_keys = set()
|
|
206
|
+
assignment_pattern = re.compile(
|
|
207
|
+
r"^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*="
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
for line in lines:
|
|
211
|
+
match = assignment_pattern.match(line)
|
|
212
|
+
key = match.group(1) if match else None
|
|
213
|
+
if key not in normalized_updates:
|
|
214
|
+
output_lines.append(line)
|
|
215
|
+
continue
|
|
216
|
+
if key in written_keys:
|
|
217
|
+
continue
|
|
218
|
+
output_lines.append(f"{key}={normalized_updates[key]}")
|
|
219
|
+
written_keys.add(key)
|
|
220
|
+
|
|
221
|
+
missing_keys = [
|
|
222
|
+
key for key in normalized_updates if key not in written_keys
|
|
223
|
+
]
|
|
224
|
+
if missing_keys and output_lines and output_lines[-1].strip():
|
|
225
|
+
output_lines.append("")
|
|
226
|
+
output_lines.extend(
|
|
227
|
+
f"{key}={normalized_updates[key]}" for key in missing_keys
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
_atomic_write_env(env_path, "\n".join(output_lines) + "\n")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def ensure_env_file(env_path=None):
|
|
234
|
+
"""Create the config and add any missing default fields without overwriting."""
|
|
235
|
+
env_path = Path(env_path or get_preferred_env_path()).expanduser()
|
|
236
|
+
env_path.parent.mkdir(parents=True, exist_ok=True)
|
|
237
|
+
created = not env_path.exists()
|
|
238
|
+
|
|
239
|
+
if created:
|
|
240
|
+
_atomic_write_env(env_path, build_env_template())
|
|
241
|
+
else:
|
|
242
|
+
existing = read_env_file(env_path)
|
|
243
|
+
missing_values = {}
|
|
244
|
+
for service in VALID_AI_SERVICES:
|
|
245
|
+
defaults = AI_SERVICE_DEFAULTS[service]
|
|
246
|
+
missing_defaults = {
|
|
247
|
+
defaults["api_key_env"]: "",
|
|
248
|
+
defaults["api_url_env"]: defaults["api_url"],
|
|
249
|
+
defaults["model_env"]: defaults["model"],
|
|
250
|
+
}
|
|
251
|
+
for key, value in missing_defaults.items():
|
|
252
|
+
if key not in existing or (value and not existing[key]):
|
|
253
|
+
missing_values[key] = value
|
|
254
|
+
if not existing.get("DEFAULT_AI_SERVICE"):
|
|
255
|
+
missing_values["DEFAULT_AI_SERVICE"] = "groq"
|
|
256
|
+
if missing_values:
|
|
257
|
+
update_env_file_values(env_path, missing_values)
|
|
258
|
+
|
|
259
|
+
_protect_env_file(env_path)
|
|
260
|
+
return env_path, created
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _protect_env_file(env_path):
|
|
264
|
+
"""Restrict config permissions to the current user on Unix-like systems."""
|
|
265
|
+
if os.name != "nt":
|
|
266
|
+
try:
|
|
267
|
+
Path(env_path).chmod(0o600)
|
|
268
|
+
except OSError:
|
|
269
|
+
pass
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _atomic_write_env(env_path, content):
|
|
273
|
+
"""Replace an env file atomically with owner-only Unix permissions."""
|
|
274
|
+
env_path = Path(env_path)
|
|
275
|
+
env_path.parent.mkdir(parents=True, exist_ok=True)
|
|
276
|
+
temp_path = None
|
|
277
|
+
try:
|
|
278
|
+
with tempfile.NamedTemporaryFile(
|
|
279
|
+
mode="w",
|
|
280
|
+
encoding="utf-8",
|
|
281
|
+
dir=env_path.parent,
|
|
282
|
+
prefix=f".{env_path.name}.",
|
|
283
|
+
delete=False,
|
|
284
|
+
) as temp_file:
|
|
285
|
+
temp_path = Path(temp_file.name)
|
|
286
|
+
temp_file.write(content)
|
|
287
|
+
temp_file.flush()
|
|
288
|
+
os.fsync(temp_file.fileno())
|
|
289
|
+
_protect_env_file(temp_path)
|
|
290
|
+
os.replace(temp_path, env_path)
|
|
291
|
+
_protect_env_file(env_path)
|
|
292
|
+
finally:
|
|
293
|
+
if temp_path and temp_path.exists():
|
|
294
|
+
try:
|
|
295
|
+
temp_path.unlink()
|
|
296
|
+
except OSError:
|
|
297
|
+
pass
|