Add .env support, Cline configuration, and dynamic branch resolution - #2
Add .env support, Cline configuration, and dynamic branch resolution#2hmshohrab wants to merge 7 commits into
Conversation
Co-Authored-By: Claude <noreply@anthropic.com>
docs: simplify README intro and add tool support badges
…rap scripts, and expand AI provider presets.
…h resolution for bootstrap script
📝 WalkthroughWalkthroughAdds a macOS/Linux Bash manager, extends the Windows PowerShell manager with Cline support, expands gateway presets, loads local environment files, adds a bootstrap launcher, and updates installation and usage documentation. ChangesConfiguration manager
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant AIConfigManagerSh
participant AIConfigPresets
participant ModelEndpoints
participant ClientConfigFiles
User->>AIConfigManagerSh: choose gateway, targets, and models
AIConfigManagerSh->>AIConfigPresets: load provider settings
AIConfigManagerSh->>ModelEndpoints: fetch available models
ModelEndpoints-->>AIConfigManagerSh: return model list
User->>AIConfigManagerSh: confirm configuration
AIConfigManagerSh->>ClientConfigFiles: write client settings and backups
ClientConfigFiles-->>AIConfigManagerSh: return paths and backup paths
AIConfigManagerSh-->>User: display completion details
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Warning |
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
README.md (1)
162-164: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUse the repository identity consistently in issue links.
These links still target
TechTronixx/Custom-modelswitch, while the rest of the README targetshmshohrab/Custom-modelswitch. Update them to the canonical repository.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@README.md` around lines 162 - 164, Update the issue and Discussions links in the README paragraph beginning “Found a bug or want a new gateway/tool?” to use the canonical hmshohrab/Custom-modelswitch repository, preserving the existing link destinations and surrounding text.AI-Config-Manager.ps1 (1)
446-476: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winCodex provider
namefallback is computed but never used, then patched with a wrong hardcoded value.
$providerName(line 447) falls back to$providerwhen$ProviderNameis empty, but the TOML section at line 461 still interpolates the raw$ProviderNameparameter, so a blank name is still written whenever the caller doesn't supply one. Line 476 then "fixes" this after the fact with a blanket regex that rewrites every blankname = ""in the whole file to the literal string"custom"— regardless of which[model_providers.<id>]section it's in. If$providerisn't actually"custom"(e.g. a real gateway id likeagentrouter), the section gets mislabeled. Worse, since the regex isn't scoped to the section just written, re-running this on a config.toml that already has other, unrelated provider sections with blank names (e.g. from an older buggy write) will silently relabel those too. The Bash counterpart avoids this by repairing the name using the matched section's own provider id, not a hardcoded literal.🐛 Proposed fix
$section = @( "[model_providers.$provider]" - "name = `"$ProviderName`"" + "name = `"$providerName`"" "base_url = `"$(Normalize-BaseUrl $BaseUrl)`"" "env_key = `"$envKey`"" ) -join "`r`n" $section += "`r`n" if ($toml -match $sectionPattern) { $toml = [regex]::Replace($toml, $sectionPattern, $section, 1) } else { if ($toml -and !$toml.EndsWith("`n")) { $toml += "`r`n" } $toml += "`r`n$section" } $toml = Set-TomlEnvPolicyValue $toml $envKey $ApiKey - $toml = [regex]::Replace($toml, '(?m)^\s*name\s*=\s*""\s*$', 'name = "custom"')With
$providerNameused at the write site, the section is never written blank in the first place, removing the need for the blunt global patch (or if legacy files must be repaired, scope it per-section using the matched provider id, mirroring the Bashrepair_tomllogic).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@AI-Config-Manager.ps1` around lines 446 - 476, Update the generated provider section in the relevant configuration-writing function to use the computed $providerName when setting the TOML name field. Remove the global regex replacement that hardcodes blank names to "custom"; do not modify unrelated provider sections, and preserve $provider as the fallback name for empty input.
🧹 Nitpick comments (2)
AI-Config-Manager.sh (2)
1426-1426: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPass
client_typeas an argv value instead of splicing it into the Python source.
'...["'$client_type'"]...'interpolates an unquoted shell variable into the program text. It's safe today becauseclient_typeis a caller-supplied literal, but it breaks the pattern used everywhere else in this file and silences nothing if the value ever becomes dynamic.♻️ Proposed refactor
- c_live=$(python3 -c 'import json, sys; print(json.dumps(json.loads(sys.argv[1])["'$client_type'"]))' "$rf") + c_live=$(python3 -c 'import json, sys; print(json.dumps(json.loads(sys.argv[1])[sys.argv[2]]))' "$rf" "$client_type")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@AI-Config-Manager.sh` at line 1426, Update the Python invocation assigning c_live so client_type is passed as a separate argv argument and accessed through sys.argv, rather than interpolated into the Python source. Preserve the existing JSON lookup behavior and rf argument handling.Source: Linters/SAST tools
1301-1307: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winReplace
evalwith indirect expansion.The value is constrained to
[A-Z0-9_]+_API_KEYby there.subat Line 1296, so this isn't currently exploitable, but${!var}(supported since Bash 2, so fine for the stated 3.2 floor) removes the injection surface entirely and is one line shorter. Note this also needs thelocalfix from the adjacent comment.♻️ Proposed refactor
env_default_key="" if [ -n "$env_var_name" ]; then - eval "env_default_key=\$$env_var_name" + env_default_key="${!env_var_name}" fi🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@AI-Config-Manager.sh` around lines 1301 - 1307, In the environment-default lookup, replace the eval-based assignment in the env_var_name branch with Bash indirect expansion using the existing variable name, while preserving the empty-value fallback to CUSTOM_API_KEY. Also apply the adjacent comment’s local-variable fix in this same block.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.env.example:
- Around line 9-12: Update the environment variable names in the example
configuration to match the preset-based auto-detection convention: replace
GEMINI_API_KEY with GOOGLE_API_KEY and replace CLOUDFLARE_API_TOKEN with the
corresponding *_API_KEY name expected by the Cloudflare preset. Keep
OPENAI_API_KEY and CUSTOM_API_KEY unchanged.
In `@AI-Config-Manager.ps1`:
- Around line 788-810: Update Load-DotEnv so it selects exactly one dotenv file:
load .env when it exists, otherwise load .env.local, and ignore .env.local
whenever .env is present. Preserve the existing parsing and process-environment
assignment behavior for the selected file.
- Around line 492-551: Update Configure-Cline to stop swallowing JSON parse
failures when loading secrets.json and globalState.json: reuse Load-JsonObject,
or otherwise throw an explicit error consistent with its “Cannot safely edit ...
because it is not strict JSON” behavior before writing replacement content. Also
wire the ProviderName parameter into the appropriate Cline state/configuration
field so caller-supplied provider names are preserved.
In `@AI-Config-Manager.sh`:
- Around line 262-268: Restore terminal settings when interrupts occur in both
affected regions of AI-Config-Manager.sh: around lines 262-268, install an
INT/EXIT trap after capturing term_state that restores it (or runs stty sane)
and exits with status 130, covering the dd read; around lines 1324-1327, install
a trap around the stty -echo/read/stty echo API-key prompt that re-enables echo
on interruption. Remove or clear each trap after normal completion so it does
not affect subsequent shell flow.
- Around line 17-37: Update load_dotenv to process both $SCRIPT_DIR/.env and
$SCRIPT_DIR/.env.local sequentially, allowing values from .env.local to override
.env, and preserve the existing parsing behavior for each file. Replace the
xargs-based key trimming with shell parameter expansion so quotes, backslashes,
and unmatched quotes are handled literally.
- Around line 1324-1327: Update the API-key prompt and the other unflagged read
sites (including the locations near lines 119, 1218, 1281, 1285, and 1414) to
use raw input mode so backslashes are preserved. Wrap the echo-disabled prompt
flow around read so terminal echo is restored even when the user interrupts,
while preserving the existing prompt and API-key handling.
- Around line 9-15: Declare Python 3 as a required dependency in the script
header and add an early preflight check for the python3 executable before any
JSON/TOML operations or argument handling proceeds. If it is unavailable, emit
one clear actionable error and exit nonzero, preventing the later call sites
from producing empty results or repeated command-not-found errors.
- Around line 1291-1318: Remove the local declarations for env_var_name,
env_default_key, and masked_env in the top-level while loop, while preserving
their initialization and assignment behavior as regular shell variables. Ensure
the .env pre-saved-key lookup and masking flow executes without emitting
local-scope errors.
- Around line 375-378: Remove the unsupported --fail-with-body option from the
curl invocation shown and from the corresponding curl call in both model-fetch
helpers. Preserve the existing HTTP status capture, timeout settings, headers,
output handling, and fallback to http_code=000.
- Around line 169-182: Update set_macos_env_var to select a persistence file
appropriate to the user’s active shell on both macOS and Linux, including bash
startup files such as ~/.bashrc or ~/.bash_profile alongside the existing zsh
files. Preserve exporting the variable for the current session, and create or
update the selected shell-sourced profile so *_API_KEY values persist in new
sessions.
- Around line 506-512: Update every configuration writer—configure_claude,
configure_claude_desktop, configure_opencode, configure_codex, and
configure_cline—to avoid passing API keys as python3 command-line arguments;
provide secrets through stdin or an exported environment variable instead. After
each JSON write, explicitly set the output file permissions to 0600 with
os.chmod, including ~/.codex/auth.json and ~/.cline/data/secrets.json.
In `@AI-Config-Presets.json`:
- Around line 261-282: Update the Gemini presets in AI-Config-Presets.json to
use the OpenAI-compatible root
https://generativelanguage.googleapis.com/v1beta/openai for client and
model-discovery requests, preventing duplicated or invalid path suffixes. For
the opencode preset, adjust the API configuration consistently with this
endpoint, including replacing the `@ai-sdk/google-specific` shape with the
supported OpenAI-compatible baseURL/apiKey options if required; update the
claude and codex entries as needed to preserve the same valid Gemini request
behavior.
- Around line 230-234: Add an explicit codex.providerKey of "custom" to the
openai preset in AI-Config-Presets.json, ensuring the main-loop environment-key
lookup matches configure_codex’s fallback and persisted CUSTOM_API_KEY setting.
In `@bootstrap.sh`:
- Around line 21-23: Update the download loop in bootstrap.sh to stage both
AI-Config-Manager.sh and AI-Config-Presets.json in a temporary directory,
preserving the existing installation while either download can fail. Replace the
installed files in $DIR only after both curl operations succeed, and clean up
the temporary staging directory on success or failure.
- Around line 13-22: Update the bootstrap download flow around BASE_URL, the
FILE loop, and the subsequent execution to use immutable, pinned release or
commit artifacts instead of mutable branch contents. Add verification of each
downloaded artifact using trusted checksums or signatures before allowing
execution, and abort immediately when pinning or verification fails.
In `@README.md`:
- Line 143: Update the self-test instruction in the README to replace “no
terminal needed” with “no interactive menu needed,” making clear that the
commands must still be run from a terminal.
- Around line 39-42: Update the macOS prerequisites section in README.md to
present python3 as an explicit required dependency rather than a built-in tool,
and document that users must install it if absent. Align the prerequisite
wording with AI-Config-Manager.sh’s preset-loading requirement.
- Around line 3-5: Align the README platform documentation with bootstrap.sh’s
advertised support by adding Linux to the platform badge, introduction,
quick-start instructions, and requirements/dependency guidance. Alternatively,
if Linux is not supported, update bootstrap.sh to advertise macOS-only support
and keep all README references consistent.
---
Outside diff comments:
In `@AI-Config-Manager.ps1`:
- Around line 446-476: Update the generated provider section in the relevant
configuration-writing function to use the computed $providerName when setting
the TOML name field. Remove the global regex replacement that hardcodes blank
names to "custom"; do not modify unrelated provider sections, and preserve
$provider as the fallback name for empty input.
In `@README.md`:
- Around line 162-164: Update the issue and Discussions links in the README
paragraph beginning “Found a bug or want a new gateway/tool?” to use the
canonical hmshohrab/Custom-modelswitch repository, preserving the existing link
destinations and surrounding text.
---
Nitpick comments:
In `@AI-Config-Manager.sh`:
- Line 1426: Update the Python invocation assigning c_live so client_type is
passed as a separate argv argument and accessed through sys.argv, rather than
interpolated into the Python source. Preserve the existing JSON lookup behavior
and rf argument handling.
- Around line 1301-1307: In the environment-default lookup, replace the
eval-based assignment in the env_var_name branch with Bash indirect expansion
using the existing variable name, while preserving the empty-value fallback to
CUSTOM_API_KEY. Also apply the adjacent comment’s local-variable fix in this
same block.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e3500f8f-ad03-4d8a-9988-8a83439fa44f
📒 Files selected for processing (7)
.env.example.gitignoreAI-Config-Manager.ps1AI-Config-Manager.shAI-Config-Presets.jsonREADME.mdbootstrap.sh
| OPENAI_API_KEY= | ||
| GEMINI_API_KEY= | ||
| CLOUDFLARE_API_TOKEN= | ||
| CUSTOM_API_KEY= |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
GEMINI_API_KEY won't be auto-detected for the google preset.
The pre-saved key lookup derives the env var from the preset id (AI-Config-Manager.sh Line 1293-1298: re.sub(r"[^A-Z0-9]", "_", id.upper()) + "_API_KEY"), so the google preset looks for GOOGLE_API_KEY, not GEMINI_API_KEY. Same for CLOUDFLARE_API_TOKEN, which never matches the *_API_KEY pattern.
🔧 Proposed fix
OPENAI_API_KEY=
-GEMINI_API_KEY=
-CLOUDFLARE_API_TOKEN=
+GOOGLE_API_KEY=
CUSTOM_API_KEY=📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| OPENAI_API_KEY= | |
| GEMINI_API_KEY= | |
| CLOUDFLARE_API_TOKEN= | |
| CUSTOM_API_KEY= | |
| OPENAI_API_KEY= | |
| GOOGLE_API_KEY= | |
| CUSTOM_API_KEY= |
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 10-10: [UnorderedKey] The GEMINI_API_KEY key should go before the HCNSEC_API_KEY key
(UnorderedKey)
[warning] 11-11: [UnorderedKey] The CLOUDFLARE_API_TOKEN key should go before the CONDUIT_API_KEY key
(UnorderedKey)
[warning] 12-12: [UnorderedKey] The CUSTOM_API_KEY key should go before the EUROMODELS_API_KEY key
(UnorderedKey)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.env.example around lines 9 - 12, Update the environment variable names in
the example configuration to match the preset-based auto-detection convention:
replace GEMINI_API_KEY with GOOGLE_API_KEY and replace CLOUDFLARE_API_TOKEN with
the corresponding *_API_KEY name expected by the Cloudflare preset. Keep
OPENAI_API_KEY and CUSTOM_API_KEY unchanged.
| function Configure-Cline { | ||
| param( | ||
| [Parameter(Mandatory)][string]$BaseUrl, | ||
| [Parameter(Mandatory)][string]$ApiKey, | ||
| [Parameter(Mandatory)][string]$Model, | ||
| [string]$ProviderName = "Custom" | ||
| ) | ||
|
|
||
| $dir = Join-Path $env:USERPROFILE ".cline\data" | ||
| if (!(Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } | ||
|
|
||
| $secretsPath = Join-Path $dir "secrets.json" | ||
| $globalPath = Join-Path $dir "globalState.json" | ||
|
|
||
| $secretsBackup = Backup-File $secretsPath | ||
| $globalBackup = Backup-File $globalPath | ||
|
|
||
| $anthropicUrl = $BaseUrl.TrimEnd("/") | ||
| if ($anthropicUrl.EndsWith("/v1")) { $anthropicUrl = $anthropicUrl.Substring(0, $anthropicUrl.Length - 3) } | ||
|
|
||
| $openaiUrl = $BaseUrl.TrimEnd("/") | ||
| if (!$openaiUrl.EndsWith("/v1")) { $openaiUrl += "/v1" } | ||
|
|
||
| $secrets = @{} | ||
| if (Test-Path $secretsPath) { | ||
| try { | ||
| $json = Get-Content $secretsPath -Raw | ConvertFrom-Json | ||
| $json.psobject.properties | ForEach-Object { $secrets[$_.Name] = $_.Value } | ||
| } catch {} | ||
| } | ||
| $secrets["apiKey"] = $ApiKey | ||
| $secrets["openAiApiKey"] = $ApiKey | ||
| $secrets["anthropicApiKey"] = $ApiKey | ||
| Set-Content -Path $secretsPath -Value ($secrets | ConvertTo-Json -Depth 5) -Encoding UTF8 | ||
|
|
||
| $state = @{} | ||
| if (Test-Path $globalPath) { | ||
| try { | ||
| $json = Get-Content $globalPath -Raw | ConvertFrom-Json | ||
| $json.psobject.properties | ForEach-Object { $state[$_.Name] = $_.Value } | ||
| } catch {} | ||
| } | ||
| $state["apiProvider"] = "openai-compatible" | ||
| $state["planModeApiProvider"] = "openai-compatible" | ||
| $state["actModeApiProvider"] = "openai-compatible" | ||
| $state["openAiBaseUrl"] = $openaiUrl | ||
| $state["anthropicBaseUrl"] = $anthropicUrl | ||
| $state["planModeOpenAiModelId"] = $Model | ||
| $state["actModeOpenAiModelId"] = $Model | ||
| $state["planModeApiModelId"] = $Model | ||
| $state["actModeApiModelId"] = $Model | ||
| $state["openAiModelId"] = $Model | ||
| Set-Content -Path $globalPath -Value ($state | ConvertTo-Json -Depth 5) -Encoding UTF8 | ||
|
|
||
| return @( | ||
| @{ Path=$secretsPath; Backup=$secretsBackup } | ||
| @{ Path=$globalPath; Backup=$globalBackup } | ||
| ) | ||
| } | ||
|
|
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Configure-Cline silently discards unparseable secrets/state files instead of failing loudly.
Lines 517-521 and 529-533 swallow JSON parse errors with empty catch {} blocks (flagged by static analysis), so a corrupted or non-JSON secrets.json/globalState.json is silently replaced with a fresh object built only from the new values — losing any other keys Cline itself may store there — with no warning to the user. This is inconsistent with Load-JsonObject elsewhere in this file, which explicitly throws ("Cannot safely edit ... because it is not strict JSON") rather than continuing with an empty object. The prior Backup-File call mitigates permanent loss, but the user gets no indication their existing config was discarded.
🛡️ Suggested fix
if (Test-Path $secretsPath) {
try {
$json = Get-Content $secretsPath -Raw | ConvertFrom-Json
$json.psobject.properties | ForEach-Object { $secrets[$_.Name] = $_.Value }
- } catch {}
+ } catch {
+ Write-Host " Warning: could not parse existing secrets.json; it will be overwritten." -ForegroundColor Yellow
+ }
}Separately, static analysis also flags the ProviderName parameter (line 497) as declared but never referenced in the function body, so custom provider names passed by callers are silently dropped for Cline (the shipped Bash configure_cline has the same no-op behavior for provider_name, so this appears to be a shared, low-impact gap rather than a new regression here).
🧰 Tools
🪛 PSScriptAnalyzer (1.25.0)
[warning] 520-520: Empty catch block is used. Please use Write-Error or throw statements in catch blocks.
(PSAvoidUsingEmptyCatchBlock)
[warning] 532-532: Empty catch block is used. Please use Write-Error or throw statements in catch blocks.
(PSAvoidUsingEmptyCatchBlock)
[warning] 497-497: The parameter 'ProviderName' has been declared but not used.
(PSReviewUnusedParameter)
[warning] 492-492: The cmdlet 'Configure-Cline' uses an unapproved verb.
(PSUseApprovedVerbs)
[warning] Missing BOM encoding for non-ASCII encoded file 'AI-Config-Manager.ps1'
(PSUseBOMForUnicodeEncodedFile)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@AI-Config-Manager.ps1` around lines 492 - 551, Update Configure-Cline to stop
swallowing JSON parse failures when loading secrets.json and globalState.json:
reuse Load-JsonObject, or otherwise throw an explicit error consistent with its
“Cannot safely edit ... because it is not strict JSON” behavior before writing
replacement content. Also wire the ProviderName parameter into the appropriate
Cline state/configuration field so caller-supplied provider names are preserved.
Source: Linters/SAST tools
| function Load-DotEnv { | ||
| $paths = @( | ||
| (Join-Path $PSScriptRoot ".env"), | ||
| (Join-Path $PSScriptRoot ".env.local") | ||
| ) | ||
| foreach ($path in $paths) { | ||
| if (Test-Path $path) { | ||
| Get-Content $path | ForEach-Object { | ||
| $line = $_.Trim() | ||
| if ($line -and !$line.StartsWith("#") -and $line.Contains("=")) { | ||
| $parts = $line.Split("=", 2) | ||
| $k = $parts[0].Trim() | ||
| $v = $parts[1].Trim().Trim('"').Trim("'") | ||
| if ($k -and $v) { | ||
| [Environment]::SetEnvironmentVariable($k, $v, "Process") | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Load-DotEnv |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Load-DotEnv precedence diverges from the Bash implementation.
This loads and applies both .env and .env.local unconditionally (line 789-792), so if both exist, .env.local values silently override .env values for any overlapping keys. The Bash load_dotenv treats these as mutually exclusive: it only loads .env.local when .env is absent, otherwise .env wins outright and .env.local is ignored entirely. This is a real cross-platform behavioral divergence for the same documented feature — a user with both files present will get different resolved API keys depending on which script they run.
🔧 Suggested fix to match Bash precedence
function Load-DotEnv {
- $paths = @(
- (Join-Path $PSScriptRoot ".env"),
- (Join-Path $PSScriptRoot ".env.local")
- )
- foreach ($path in $paths) {
- if (Test-Path $path) {
+ $envPath = Join-Path $PSScriptRoot ".env"
+ $envLocalPath = Join-Path $PSScriptRoot ".env.local"
+ $path = if (Test-Path $envPath) { $envPath } elseif (Test-Path $envLocalPath) { $envLocalPath } else { $null }
+ if ($path) {
Get-Content $path | ForEach-Object {
$line = $_.Trim()
if ($line -and !$line.StartsWith("#") -and $line.Contains("=")) {
$parts = $line.Split("=", 2)
$k = $parts[0].Trim()
$v = $parts[1].Trim().Trim('"').Trim("'")
if ($k -and $v) {
[Environment]::SetEnvironmentVariable($k, $v, "Process")
}
}
}
- }
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function Load-DotEnv { | |
| $paths = @( | |
| (Join-Path $PSScriptRoot ".env"), | |
| (Join-Path $PSScriptRoot ".env.local") | |
| ) | |
| foreach ($path in $paths) { | |
| if (Test-Path $path) { | |
| Get-Content $path | ForEach-Object { | |
| $line = $_.Trim() | |
| if ($line -and !$line.StartsWith("#") -and $line.Contains("=")) { | |
| $parts = $line.Split("=", 2) | |
| $k = $parts[0].Trim() | |
| $v = $parts[1].Trim().Trim('"').Trim("'") | |
| if ($k -and $v) { | |
| [Environment]::SetEnvironmentVariable($k, $v, "Process") | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| Load-DotEnv | |
| function Load-DotEnv { | |
| $envPath = Join-Path $PSScriptRoot ".env" | |
| $envLocalPath = Join-Path $PSScriptRoot ".env.local" | |
| $path = if (Test-Path $envPath) { $envPath } elseif (Test-Path $envLocalPath) { $envLocalPath } else { $null } | |
| if ($path) { | |
| Get-Content $path | ForEach-Object { | |
| $line = $_.Trim() | |
| if ($line -and !$line.StartsWith("#") -and $line.Contains("=")) { | |
| $parts = $line.Split("=", 2) | |
| $k = $parts[0].Trim() | |
| $v = $parts[1].Trim().Trim('"').Trim("'") | |
| if ($k -and $v) { | |
| [Environment]::SetEnvironmentVariable($k, $v, "Process") | |
| } | |
| } | |
| } | |
| } | |
| } | |
| Load-DotEnv |
🧰 Tools
🪛 PSScriptAnalyzer (1.25.0)
[warning] 788-788: The cmdlet 'Load-DotEnv' uses an unapproved verb.
(PSUseApprovedVerbs)
[warning] Missing BOM encoding for non-ASCII encoded file 'AI-Config-Manager.ps1'
(PSUseBOMForUnicodeEncodedFile)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@AI-Config-Manager.ps1` around lines 788 - 810, Update Load-DotEnv so it
selects exactly one dotenv file: load .env when it exists, otherwise load
.env.local, and ignore .env.local whenever .env is present. Preserve the
existing parsing and process-environment assignment behavior for the selected
file.
| # Requires Bash 3.2+ and curl. Existing config files are backed up before every write. | ||
| # | ||
| # Usage: ./AI-Config-Manager.sh | ||
| # Selftest: ./AI-Config-Manager.sh -SelfTest | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| PRESETS_FILE="$SCRIPT_DIR/AI-Config-Presets.json" |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Undeclared hard dependency on python3.
The script shells out to python3 for every JSON/TOML operation (~40 call sites), but the header only claims Bash 3.2 + curl, and there's no preflight check. macOS hasn't shipped a python3 in PATH by default since the Xcode CLT split, so users get a stream of command not found with empty results rather than a clear error.
🔧 Proposed fix
-# Requires Bash 3.2+ and curl. Existing config files are backed up before every write.
+# Requires Bash 3.2+, curl, and python3. Existing config files are backed up before every write.
#
# Usage: ./AI-Config-Manager.sh
# Selftest: ./AI-Config-Manager.sh -SelfTest
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PRESETS_FILE="$SCRIPT_DIR/AI-Config-Presets.json"
+
+for _dep in python3 curl; do
+ command -v "$_dep" >/dev/null 2>&1 || {
+ echo "Required dependency not found: $_dep" >&2
+ exit 1
+ }
+done📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Requires Bash 3.2+ and curl. Existing config files are backed up before every write. | |
| # | |
| # Usage: ./AI-Config-Manager.sh | |
| # Selftest: ./AI-Config-Manager.sh -SelfTest | |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| PRESETS_FILE="$SCRIPT_DIR/AI-Config-Presets.json" | |
| # Requires Bash 3.2+, curl, and python3. Existing config files are backed up before every write. | |
| # | |
| # Usage: ./AI-Config-Manager.sh | |
| # Selftest: ./AI-Config-Manager.sh -SelfTest | |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| PRESETS_FILE="$SCRIPT_DIR/AI-Config-Presets.json" | |
| for _dep in python3 curl; do | |
| command -v "$_dep" >/dev/null 2>&1 || { | |
| echo "Required dependency not found: $_dep" >&2 | |
| exit 1 | |
| } | |
| done |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@AI-Config-Manager.sh` around lines 9 - 15, Declare Python 3 as a required
dependency in the script header and add an early preflight check for the python3
executable before any JSON/TOML operations or argument handling proceeds. If it
is unavailable, emit one clear actionable error and exit nonzero, preventing the
later call sites from producing empty results or repeated command-not-found
errors.
| load_dotenv() { | ||
| local env_file="" | ||
| if [ -f "$SCRIPT_DIR/.env" ]; then | ||
| env_file="$SCRIPT_DIR/.env" | ||
| elif [ -f "$SCRIPT_DIR/.env.local" ]; then | ||
| env_file="$SCRIPT_DIR/.env.local" | ||
| fi | ||
|
|
||
| if [ -n "$env_file" ]; then | ||
| while IFS='=' read -r key val || [ -n "$key" ]; do | ||
| key=$(echo "$key" | xargs) | ||
| if [[ -z "$key" || "$key" =~ ^# ]]; then | ||
| continue | ||
| fi | ||
| val=$(echo "$val" | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//") | ||
| if [ -n "$key" ] && [ -n "$val" ]; then | ||
| export "$key=$val" | ||
| fi | ||
| done < "$env_file" | ||
| fi | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
.env.local is ignored whenever .env exists — diverges from the PowerShell manager.
AI-Config-Manager.ps1 Load-DotEnv loads both files in sequence so .env.local overrides .env. Here the elif makes them mutually exclusive, and .env.example (Line 2) tells users either file works.
Also, key=$(echo "$key" | xargs) uses xargs for trimming: it interprets quotes/backslashes and aborts on an unmatched quote. Prefer parameter expansion.
🔧 Proposed fix
load_dotenv() {
- local env_file=""
- if [ -f "$SCRIPT_DIR/.env" ]; then
- env_file="$SCRIPT_DIR/.env"
- elif [ -f "$SCRIPT_DIR/.env.local" ]; then
- env_file="$SCRIPT_DIR/.env.local"
- fi
-
- if [ -n "$env_file" ]; then
+ local env_file
+ for env_file in "$SCRIPT_DIR/.env" "$SCRIPT_DIR/.env.local"; do
+ [ -f "$env_file" ] || continue
while IFS='=' read -r key val || [ -n "$key" ]; do
- key=$(echo "$key" | xargs)
+ key="${key#"${key%%[![:space:]]*}"}"
+ key="${key%"${key##*[![:space:]]}"}"
if [[ -z "$key" || "$key" =~ ^# ]]; then
continue
fi
val=$(echo "$val" | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//")
if [ -n "$key" ] && [ -n "$val" ]; then
export "$key=$val"
fi
done < "$env_file"
- fi
+ done
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| load_dotenv() { | |
| local env_file="" | |
| if [ -f "$SCRIPT_DIR/.env" ]; then | |
| env_file="$SCRIPT_DIR/.env" | |
| elif [ -f "$SCRIPT_DIR/.env.local" ]; then | |
| env_file="$SCRIPT_DIR/.env.local" | |
| fi | |
| if [ -n "$env_file" ]; then | |
| while IFS='=' read -r key val || [ -n "$key" ]; do | |
| key=$(echo "$key" | xargs) | |
| if [[ -z "$key" || "$key" =~ ^# ]]; then | |
| continue | |
| fi | |
| val=$(echo "$val" | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//") | |
| if [ -n "$key" ] && [ -n "$val" ]; then | |
| export "$key=$val" | |
| fi | |
| done < "$env_file" | |
| fi | |
| } | |
| load_dotenv() { | |
| local env_file | |
| for env_file in "$SCRIPT_DIR/.env" "$SCRIPT_DIR/.env.local"; do | |
| [ -f "$env_file" ] || continue | |
| while IFS='=' read -r key val || [ -n "$key" ]; do | |
| key="${key#"${key%%[![:space:]]*}"}" | |
| key="${key%"${key##*[![:space:]]}"}" | |
| if [[ -z "$key" || "$key" =~ ^# ]]; then | |
| continue | |
| fi | |
| val=$(echo "$val" | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//") | |
| if [ -n "$key" ] && [ -n "$val" ]; then | |
| export "$key=$val" | |
| fi | |
| done < "$env_file" | |
| done | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@AI-Config-Manager.sh` around lines 17 - 37, Update load_dotenv to process
both $SCRIPT_DIR/.env and $SCRIPT_DIR/.env.local sequentially, allowing values
from .env.local to override .env, and preserve the existing parsing behavior for
each file. Replace the xargs-based key trimming with shell parameter expansion
so quotes, backslashes, and unmatched quotes are handled literally.
| if ! curl -fsI "https://raw.githubusercontent.com/$REPO/main/AI-Config-Manager.sh" >/dev/null 2>&1; then | ||
| BRANCH="mac/linux-version" | ||
| fi | ||
|
|
||
| BASE_URL="https://raw.githubusercontent.com/$REPO/$BRANCH" | ||
| TS=$(date +%s) | ||
|
|
||
| echo "Downloading AI Config Manager from $REPO ($BRANCH)..." | ||
| for FILE in "AI-Config-Manager.sh" "AI-Config-Presets.json"; do | ||
| curl -fsSL "$BASE_URL/$FILE?v=$TS" -o "$DIR/$FILE" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Pin and verify downloaded code before executing it.
This launcher selects mutable branch contents, downloads them over the network, and immediately executes the script. Pin the artifacts to a known commit or release and verify checksums/signatures before exec to reduce supply-chain compromise risk.
Also applies to: 25-28
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bootstrap.sh` around lines 13 - 22, Update the bootstrap download flow around
BASE_URL, the FILE loop, and the subsequent execution to use immutable, pinned
release or commit artifacts instead of mutable branch contents. Add verification
of each downloaded artifact using trusted checksums or signatures before
allowing execution, and abort immediately when pinning or verification fails.
| for FILE in "AI-Config-Manager.sh" "AI-Config-Presets.json"; do | ||
| curl -fsSL "$BASE_URL/$FILE?v=$TS" -o "$DIR/$FILE" | ||
| done |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Stage downloads before replacing the current installation.
Each curl -o truncates the existing file immediately. If the second download fails, the directory can contain a new manager paired with stale or missing presets. Download into a temporary directory and replace the installed files only after both downloads succeed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bootstrap.sh` around lines 21 - 23, Update the download loop in bootstrap.sh
to stage both AI-Config-Manager.sh and AI-Config-Presets.json in a temporary
directory, preserving the existing installation while either download can fail.
Replace the installed files in $DIR only after both curl operations succeed, and
clean up the temporary staging directory on success or failure.
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Document Linux support consistently.
bootstrap.sh advertises macOS/Linux support, but the README badge, introduction, quick start, and requirements list only Windows/macOS. Add Linux installation and dependency guidance, or make the launcher’s support claim macOS-only.
Also applies to: 16-16, 23-46
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 3 - 5, Align the README platform documentation with
bootstrap.sh’s advertised support by adding Linux to the platform badge,
introduction, quick-start instructions, and requirements/dependency guidance.
Alternatively, if Linux is not supported, update bootstrap.sh to advertise
macOS-only support and keep all README references consistent.
| ### macOS | ||
| - macOS (Intel or Apple Silicon) | ||
| - Bash 3.2+ (built-in standard shell) | ||
| - `curl` and `python3` (built-in standard tools) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Do not describe python3 as a built-in macOS tool.
AI-Config-Manager.sh invokes python3 while loading presets, so the manager fails on systems without Python 3. Make it an explicit prerequisite and/or add an actionable preflight check.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 39 - 42, Update the macOS prerequisites section in
README.md to present python3 as an explicit required dependency rather than a
built-in tool, and document that users must install it if absent. Align the
prerequisite wording with AI-Config-Manager.sh’s preset-loading requirement.
|
|
||
| ## Development | ||
|
|
||
| Run the built-in self-test for the scroll-window math (no terminal needed): |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Replace the “no terminal needed” wording.
The commands below must be run from a terminal; “no interactive menu needed” is the accurate description.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` at line 143, Update the self-test instruction in the README to
replace “no terminal needed” with “no interactive menu needed,” making clear
that the commands must still be run from a terminal.
|
Hi , Thanks for sharing , Can you please follow the format , would be easier to review |
This pull request brings macOS/Bash support to the AI Config Manager project, transforming it from Windows-only to a cross-platform tool.
Key Changes:
New Files:
AI-Config-Manager.sh (~1,580 lines) — Complete Bash rewrite for macOS, mirroring all PowerShell functionality
bootstrap.sh — One-liner launcher for macOS (downloads and runs the script)
.env.example — Template for environment variable configuration
Modified Files:
AI-Config-Manager.ps1 (Windows PowerShell):
Added Configure-Cline function (Cline extension configuration)
Integrated .env / .env.local file loading (Load-DotEnv)
Updated menu to include "Configure Cline" and "Configure All" options
Enhanced "Show-Current" to display Cline configuration
Improved Codex provider key handling with fallback logic
Support for pre-saved API keys from .env files
.gitignore:
Added .env, .env.local, and *.env to exclusions (for API keys)
AI-Config-Presets.json:
Updated AgentRouter label and curated models
Added 4 new gateway presets:
Forge Gateway (Claude-focused)
Conduit AI
HCNSEC AI
OpenAI Official
Google Gemini
README.md:
Added macOS quick-start section (curl-based)
Updated badges (Bash, platform coverage)
Documented macOS requirements and setup
Updated repository URLs to hmshohrab/Custom-modelswitch
Expanded documentation for Cline and cross-platform behavior
Features Added:
✅ Cline support — Configures VS Code / CLI Cline extension
✅ .env file support — Load API keys from .env / .env.local
✅ macOS Bash version — Full feature parity with PowerShell
✅ New gateway presets — Multiple provider options
✅ Cross-platform — Windows + macOS with one unified UX
Scope:
Files changed: 8
Lines added: ~1,900+
Lines removed: ~15
This is a significant expansion that brings the tool to macOS users while maintaining 100% backward compatibility with the Windows version.
Summary by CodeRabbit
New Features
Documentation
Security