Skip to content

Add .env support, Cline configuration, and dynamic branch resolution - #2

Open
hmshohrab wants to merge 7 commits into
TechTronixx:mainfrom
hmshohrab:main
Open

Add .env support, Cline configuration, and dynamic branch resolution#2
hmshohrab wants to merge 7 commits into
TechTronixx:mainfrom
hmshohrab:main

Conversation

@hmshohrab

@hmshohrab hmshohrab commented Jul 26, 2026

Copy link
Copy Markdown

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

    • Added a macOS/Linux configuration manager for Claude Code, OpenCode, Codex, Claude Desktop, and Cline.
    • Added Cline configuration and “Configure All” workflows.
    • Added gateway presets, model discovery, custom model selection, backups, and configuration summaries.
    • Added one-click macOS/Linux bootstrap installation.
    • Added expanded provider presets and cross-platform environment variable loading.
  • Documentation

    • Updated setup, installation, requirements, usage, and platform-specific configuration guidance.
  • Security

    • Added environment variable templates and excluded local secrets files from version control.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds 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.

Changes

Configuration manager

Layer / File(s) Summary
Gateway inputs and presets
.env.example, .gitignore, AI-Config-Presets.json
Adds environment variable placeholders, ignores local secret files, and defines additional gateway/provider presets and curated models.
Bash menus and model discovery
AI-Config-Manager.sh
Adds dotenv loading, terminal menus, scrolling self-tests, preset selection, live or curated model discovery, and shared URL, masking, backup, and shell-environment helpers.
Bash client configuration
AI-Config-Manager.sh
Adds configuration writers for Claude Code, Claude Desktop, OpenCode, Codex, and Cline, including backups and provider-specific JSON, TOML, and shell updates.
Bash application flow
AI-Config-Manager.sh
Connects target and model selection to configuration writers and adds confirmation, apply handling, and masked current-configuration reporting.
PowerShell client integration
AI-Config-Manager.ps1
Adds Cline configuration and reporting, dotenv loading, Codex provider/key handling, and Cline menu, model-selection, confirmation, and apply wiring.
Bootstrap and usage documentation
bootstrap.sh, README.md
Adds a branch-aware macOS/Linux bootstrap launcher and documents cross-platform setup, targets, environment handling, and self-testing.

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
Loading

Suggested reviewers: techtronixx

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects major changes in the PR: .env loading, Cline setup, and bootstrap branch resolution.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Warning

⚠️ This pull request shows signs of AI-generated slop (defensive_cruft). It has been flagged by CodeRabbit slop detection and should be reviewed carefully.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Use the repository identity consistently in issue links.

These links still target TechTronixx/Custom-modelswitch, while the rest of the README targets hmshohrab/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 win

Codex provider name fallback is computed but never used, then patched with a wrong hardcoded value.

$providerName (line 447) falls back to $provider when $ProviderName is empty, but the TOML section at line 461 still interpolates the raw $ProviderName parameter, 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 blank name = "" in the whole file to the literal string "custom" — regardless of which [model_providers.<id>] section it's in. If $provider isn't actually "custom" (e.g. a real gateway id like agentrouter), 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 $providerName used 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 Bash repair_toml logic).

🤖 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 win

Pass client_type as 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 because client_type is 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 win

Replace eval with indirect expansion.

The value is constrained to [A-Z0-9_]+_API_KEY by the re.sub at 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 the local fix 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6e39126 and 5ef0fe7.

📒 Files selected for processing (7)
  • .env.example
  • .gitignore
  • AI-Config-Manager.ps1
  • AI-Config-Manager.sh
  • AI-Config-Presets.json
  • README.md
  • bootstrap.sh

Comment thread .env.example
Comment on lines +9 to +12
OPENAI_API_KEY=
GEMINI_API_KEY=
CLOUDFLARE_API_TOKEN=
CUSTOM_API_KEY=

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

Comment thread AI-Config-Manager.ps1
Comment on lines +492 to +551
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 }
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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

Comment thread AI-Config-Manager.ps1
Comment on lines +788 to +810
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Suggested change
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.

Comment thread AI-Config-Manager.sh
Comment on lines +9 to +15
# 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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.

Suggested change
# 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.

Comment thread AI-Config-Manager.sh
Comment on lines +17 to +37
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
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

Comment thread bootstrap.sh
Comment on lines +13 to +22
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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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.

Comment thread bootstrap.sh
Comment on lines +21 to +23
for FILE in "AI-Config-Manager.sh" "AI-Config-Presets.json"; do
curl -fsSL "$BASE_URL/$FILE?v=$TS" -o "$DIR/$FILE"
done

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Comment thread README.md
Comment on lines +3 to +5
![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS-0078D6?logo=apple&logoColor=white)
![PowerShell](https://img.shields.io/badge/PowerShell-5.1%2B-012456?logo=powershell&logoColor=white)
![Platform](https://img.shields.io/badge/platform-Windows-0078D6?logo=windows&logoColor=white)
![Issues](https://img.shields.io/github/issues/TechTronixx/Custom-modelswitch)
![Stars](https://img.shields.io/github/stars/TechTronixx/Custom-modelswitch)
![Last commit](https://img.shields.io/github/last-commit/TechTronixx/Custom-modelswitch)
![Bash](https://img.shields.io/badge/Bash-3.2%2B-4EAA25?logo=gnu-bash&logoColor=white)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Comment thread README.md
Comment on lines +39 to +42
### macOS
- macOS (Intel or Apple Silicon)
- Bash 3.2+ (built-in standard shell)
- `curl` and `python3` (built-in standard tools)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.

Comment thread README.md

## Development

Run the built-in self-test for the scroll-window math (no terminal needed):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

@TechTronixx TechTronixx added the enhancement New feature or request label Jul 26, 2026
@TechTronixx

Copy link
Copy Markdown
Owner

Hi , Thanks for sharing , Can you please follow the format , would be easier to review
https://github.com/TechTronixx/Custom-modelswitch/blob/main/CONTRIBUTING.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants