refactor: align with synu's UX and architecture

Amolith created

This overhaul brings synclaude in line with synu's patterns for
consistency across the two wrappers. Major changes include:

**Caching System**
- Add persistent model preferences in ~/.config/synclaude/models.conf
- New helper functions: _synclaude_cache_get, _synclaude_cache_set,
_synclaude_default
- Cache respects XDG_CONFIG_HOME when set
- Interactive mode now offers "Save as defaults?" prompt

**Flag Restructuring** The group flags now match synu's
Heavy/Medium/Light naming:
- OLD: -L/--large (opus, sonnet, agent), -l/--light (haiku), -H/--haiku
- NEW: -H/--heavy (opus), -M/--medium (sonnet, agent), -l/--light
  (haiku),
-k/--haiku

This better reflects the actual model tiers and separates Sonnet from
Opus.

**Default Model Changes** Per-tier fallbacks instead of single default:
- Opus: hf:moonshotai/Kimi-K2-Thinking (was GLM-4.6)
- Sonnet: hf:zai-org/GLM-4.6 (unchanged)
- Haiku: hf:deepseek-ai/DeepSeek-V3.1-Terminus (was GLM-4.6)
- Sub-agent: hf:zai-org/GLM-4.6 (unchanged)

**Usage Subcommand** `synclaude u` displays current quota usage with
badge-style display.

**Quota Display** Changed from line-based colors to badge-style with
background color:
- OLD: "Remaining: [colored]580/1000[/colored] requests"
- NEW: "Overall: [badge 42%] (580/1000 remaining)"

**Interactive Mode Enhancements**
- Filter headers now show current model IDs
- Added gum availability check with helpful error message
- Groups renamed: "Large" -> "Heavy", added "Medium" group

**Code Quality**
- Extracted quota fetching to _synclaude_get_quota helper
- Added SYNTHETIC_API_KEY validation
- Better error handling with warnings instead of silent failures
- Consistent use of curl -f flag for HTTP error handling

Assisted-by: Claude Opus 4.5 via Crush

Change summary

AGENTS.md                  | 130 +++----------
README.md                  |  83 ++++++--
completions/synclaude.fish |  10 
demo.tape                  |  10 
functions/synclaude.fish   | 359 +++++++++++++++++++++++++++++++--------
5 files changed, 385 insertions(+), 207 deletions(-)

Detailed changes

AGENTS.md 🔗

@@ -8,124 +8,60 @@ SPDX-License-Identifier: Unlicense
 
 ## Project Overview
 
-**synclaude.fish** is a Fish shell wrapper for the `claude` CLI that routes requests through [Synthetic.new](https://synthetic.new)'s API. It enables using alternative AI models (like GLM-4.6, Llama, Qwen) through the Claude CLI by proxying requests through Synthetic's Anthropic-compatible endpoint.
+**synclaude.fish** is a Fish shell wrapper for the `claude` CLI that routes requests through [Synthetic.new](https://synthetic.new)'s API. It enables using alternative AI models through the Claude CLI by proxying requests through Synthetic's Anthropic-compatible endpoint.
 
 ### Key Functionality
 
-- Defaults all Claude model tiers (Opus, Sonnet, Haiku, Sub-agent) to `hf:zai-org/GLM-4.6`
+- Configurable model defaults for each Claude tier with persistent caching
 - Provides interactive TUI for model selection using `gum`
-- Supports group-based (`--large`/`--light`) and individual model overrides
+- Supports group-based (`--heavy`/`--medium`/`--light`) and individual model overrides
 - Displays session API usage and remaining quota after each run
+- `synclaude u` shows current quota usage
 - Wraps `claude` CLI completely - all unknown flags pass through
 
-## Project Structure
+### Default Models (Fallbacks)
 
-This is a standard Fish plugin with the following structure:
-
-```
-synclaude.fish/
-├── README.md              # User documentation
-├── functions/
-│   └── synclaude.fish     # Main wrapper function (~156 lines)
-└── completions/
-    └── synclaude.fish     # Tab completions (~15 lines)
-```
+- **Opus**: `hf:moonshotai/Kimi-K2-Thinking`
+- **Sonnet**: `hf:zai-org/GLM-4.6`
+- **Haiku**: `hf:deepseek-ai/DeepSeek-V3.1-Terminus`
+- **Sub-agent**: `hf:zai-org/GLM-4.6`
 
 ### Architecture
 
 **Control Flow:**
 
-1. **Interactive mode** (`synclaude i`):
-- Fetches available models from Synthetic API
-- Prompts user to choose Groups vs Individual models
-- Prompts for which groups/models to override
-- For each selection, filters available models with `gum filter`
-- Recursively calls `synclaude` with generated flags (lines 83-84)
-2. **Flag-based mode** (default):
-- Parses custom flags with `argparse --ignore-unknown` (lines 91-94)
-- Sets environment variables for Anthropic endpoint and model overrides
-- Fetches initial quota from Synthetic API
-- Executes `command claude $argv` (line 128)
-- Fetches final quota and displays usage stats with color-coding
+1. **Usage mode** (`synclaude u`):
+   - Fetches and displays current quota usage with colored badge
+2. **Interactive mode** (`synclaude i`):
+   - Fetches available models from Synthetic API
+   - Prompts user to choose Groups vs Individual models
+   - Prompts for which groups/models to override
+   - Shows current defaults in filter headers
+   - Offers to save selections as new defaults
+   - Recursively calls `synclaude` with generated flags
+3. **Flag-based mode** (default):
+   - Parses custom flags with `argparse --ignore-unknown`
+   - Loads defaults from cache (or fallbacks)
+   - Applies group then individual overrides
+   - Sets environment variables for Anthropic endpoint
+   - Fetches initial quota from Synthetic API
+   - Executes `command claude $argv`
+   - Fetches final quota and displays usage stats with badge-style color-coding
 
 **Key Design Patterns:**
 
-- **Override precedence**: Individual flags override group flags, which override defaults (lines 107-121)
+- **Caching**: Persistent model preferences stored in `~/.config/synclaude/models.conf`
+- **Override precedence**: Individual flags override group flags, which override cached/fallback defaults
 - **Error propagation**: Uses `or return` throughout to exit on command failures
 
-## Code Conventions
-
-### Fish Shell Patterns
-
-- **Local variables**: Always use `set -l` for function-local scope
-- **Export variables**: Use `set -lx` for locally-scoped exports (lines 96-104)
-- **Variable checking**: Use `set -q variable` to test existence (lines 107-121)
-- **Array removal**: Use `set -e argv[1]` to remove elements (line 6)
-- **Command substitution**: Use `(command)` not backticks
-- **Error handling**: Chain commands with `or return` for early exits
-- **Math operations**: Use `math` builtin with `-s0` for integer results (line 139)
-
-### Code Style
-
-- **Indentation**: 4 spaces (not tabs)
-- **Line length**: No hard limit, but keep logical blocks readable
-- **Variable naming**: 
-  - Descriptive names: `models_json`, `initial_requests`, `quota_color`
-  - No abbreviations except common ones: `args` → `argv`, `arg` → `argv`
-- **Comments**: 
-  - Group logic blocks with descriptive comments (lines 88-90)
-  - Inline comments for non-obvious conditions (lines 125, 132)
-  - No redundant comments explaining obvious code
-
-### API Interaction Patterns
-
-All API calls wrapped in `gum spin` for UX:
-
-```fish
-set -l response (gum spin --spinner dot --title "Message..." -- curl -s -H "Authorization: Bearer $KEY" https://api.url)
-```
-
-API responses always:
-1. Use `jq -r` for raw (unquoted) output
-2. Provide fallback values with `// 0` or similar (lines 125, 132-133)
-3. Test for empty values after extraction (line 126)
-4. Redirect stderr to `/dev/null` if errors are expected (lines 124, 131)
-
-### Error Handling
-
-- **API failures**: Use `or return 1` to exit on curl/jq errors
-- **Null safety**: Always test extracted values before use (line 126)
-- **Propagate exit codes**: Return `$status` from wrapped command (line 84)
-- **Silent failures**: Only suppress stderr when errors are expected and handled
-
-## Completions
-
-File: `completions/synclaude.fish`
-
-- **Inherits from wrapped command**: `complete -c synclaude -w claude` (line 2)
-- **Subcommand prevention**: Use `-n "not __fish_seen_subcommand_from i"` to prevent completions after `i` (line 5)
-- **Flag completions**: `-s` (short), `-l` (long), `-r` (requires argument), `-d` (description)
-
-## Quota Display Logic
-
-Post-execution, displays:
-- **Session usage**: `final_requests - initial_requests`
-- **Remaining quota**: `limit - final_requests` (color-coded)
-
-Color coding (lines 141-146):
-- **Green**: <33% used
-- **Yellow**: 33-66% used  
-- **Red**: >66% used
-
-Color logic uses `set_color` and `set_color normal` to wrap output (lines 151-153).
-
 ## Common Gotchas
 
-1. **`argparse --ignore-unknown` is critical**: Without it, unknown flags would error instead of passing through to `claude` (line 91)
-2. **Recursive call must use function name, not alias**: Line 83 calls `synclaude`, not `command synclaude` - this works because Fish functions can recursively invoke themselves
-3. **`_flag_*` variable naming**: `argparse` creates variables from flag names with `_flag_` prefix - must use exact names (lines 107-121)
-4. **Model ID vs Model Name**: Interactive mode fetches model names for display but must extract model IDs for API use (lines 30, 37, 53, 60, 67, 74)
+1. **`argparse --ignore-unknown` is critical**: Without it, unknown flags would error instead of passing through to `claude`
+2. **Recursive call must use function name, not alias**: `synclaude` not `command synclaude` - this works because Fish functions can recursively invoke themselves
+3. **`_flag_*` variable naming**: `argparse` creates variables from flag names with `_flag_` prefix - must use exact names
+4. **Model ID vs Model Name**: Interactive mode fetches model names for display but must extract model IDs for API use
 5. **jq requires argument quoting**: In jq, use `--arg name "$var"` then `$name` in filter - don't interpolate directly into filter string
 6. **Quota check timing**: Initial quota fetch happens before `claude` runs to calculate session usage. If it fails, script continues anyway
 7. **`math` precision**: Use `-s0` for integer output to avoid decimal points in quota percentages
 8. **Local exports don't persist**: `set -lx` variables only exist in function scope - won't affect parent shell
+9. **Cache file location**: Respects `XDG_CONFIG_HOME` if set, otherwise defaults to `~/.config/synclaude/`

README.md 🔗

@@ -10,15 +10,15 @@ A Fish wrapper for the `claude` CLI that routes requests through
 [Synthetic.new](https://synthetic.new), enabling use of models like GLM-4.6
 through `claude`.
 
-![Invoking synclaude with the interactive i subcommand to override the default models. First question is how to override the models, selecting each individually or by group. After selecting group, the next question is which group, large -L or light -l or both. After selecting large, the next question is which model to use, presented as a list of IDs with fuzzy filtering. After typing "mmm2" for MiniMax M2, Claude Code starts. After typing Hi and getting a response and quitting Claude Code, synclaude shows how many requests were used during the session and how many are remaining.](https://vhs.charm.sh/vhs-2wRBg9PpDidQIfCptnQuLz.gif)
+![Invoking synclaude with the interactive i subcommand to override the default models. First question is how to override the models, selecting each individually or by group. After selecting group, the next question is which group, large -L or light -l or both. After selecting large, the next question is which model to use, presented as a list of IDs with fuzzy filtering. After typing "mmm2" for MiniMax M2, Claude Code starts. After typing Hi and getting a response and quitting Claude Code, synclaude shows how many requests were used during the session and how many are remaining.](https://vhs.charm.sh/vhs-M1NGelKGg1PdjB3sN53De.gif)
 
 ## tl;dr
 
-- Defaults to GLM 4.6
+- Configurable defaults per model tier with persistent caching
 - Displays session usage and remaining API quota after each run
-- Invoke as `synclaude i` for an interactive model picker
-- Configure models by group or individually through the interactive picker or
-  flags
+- `synclaude u` shows current quota
+- Invoke as `synclaude i` for an interactive model picker with save-as-default option
+- Configure models by group or individually through the interactive picker or flags
 
 ## Requirements
 
@@ -52,6 +52,15 @@ set -gx SYNTHETIC_API_KEY your_api_key_here
 
 ## Usage
 
+### Check quota
+
+Run with `u` subcommand to see current API usage:
+
+```fish
+synclaude u
+# Usage:  42%  (580/1000 remaining)
+```
+
 ### Basic
 
 Use `synclaude` exactly like you would use `claude`:
@@ -64,8 +73,16 @@ synclaude
 synclaude -p "What does functions/synclaude.fish do?"
 ```
 
-By default, all models (Opus, Sonnet, Haiku, and Sub-agent) use
-`hf:zai-org/GLM-4.6`.
+### Default Models
+
+| Tier | Default Model |
+|------|--------------|
+| Opus | `hf:moonshotai/Kimi-K2-Thinking` |
+| Sonnet | `hf:zai-org/GLM-4.6` |
+| Haiku | `hf:deepseek-ai/DeepSeek-V3.1-Terminus` |
+| Sub-agent | `hf:zai-org/GLM-4.6` |
+
+Defaults can be overridden with flags or saved persistently through interactive mode.
 
 ### Interactive overrides
 
@@ -77,18 +94,27 @@ synclaude i
 
 1. Select whether to override models by group or by specific model
 2. Select which ones to override
-3. Select what to override them with
+3. Select what to override them with (shows current defaults in header)
+4. Optionally save selections as new defaults
 
 ### Flags
 
-| Flag       | Short | Description                                 |
-| ---------- | ----- | ------------------------------------------- |
-| `--large`  | `-L`  | Override Opus, Sonnet, and Sub-agent models |
-| `--light`  | `-l`  | Override Haiku                              |
-| `--haiku`  | `-H`  | Override Haiku                              |
-| `--opus`   | `-o`  | Override Opus                               |
-| `--sonnet` | `-s`  | Override Sonnet                             |
-| `--agent`  | `-a`  | Override sub-agent                          |
+#### Group flags
+
+| Flag | Short | Description |
+|------|-------|-------------|
+| `--heavy` | `-H` | Override Opus model |
+| `--medium` | `-M` | Override Sonnet and Sub-agent models |
+| `--light` | `-l` | Override Haiku model |
+
+#### Individual flags
+
+| Flag | Short | Description |
+|------|-------|-------------|
+| `--opus` | `-o` | Override Opus model |
+| `--sonnet` | `-s` | Override Sonnet model |
+| `--haiku` | `-k` | Override Haiku model |
+| `--agent` | `-a` | Override Sub-agent model |
 
 All other flags are passed through to the `claude` command.
 
@@ -97,27 +123,30 @@ All other flags are passed through to the `claude` command.
 #### By group
 
 ```fish
-# Set all "large" models (Opus, Sonnet, Sub-agent) to GLM-4.6
-synclaude --large hf:zai-org/GLM-4.6 "Your prompt"
+# Set heavy model (Opus) to a specific model
+synclaude --heavy hf:moonshotai/Kimi-K2-Thinking "Your prompt"
 
-# Set the "light" model (Haiku)
+# Set medium models (Sonnet, Sub-agent) to GLM-4.6
+synclaude --medium hf:zai-org/GLM-4.6 "Your prompt"
+
+# Set light model (Haiku)
 synclaude --light hf:qwen/Qwen2.5-Coder-32B-Instruct "Your prompt"
 
-# Combine both
-synclaude --large hf:zai-org/GLM-4.6 --light hf:qwen/Qwen2.5-Coder-32B-Instruct "Your prompt"
+# Combine groups
+synclaude --heavy hf:moonshotai/Kimi-K2-Thinking --medium hf:zai-org/GLM-4.6 "Your prompt"
 ```
 
 #### By individual model
 
 ```fish
 # Override specific models
-synclaude --opus hf:zai-org/GLM-4.6 "Your prompt"
-synclaude --sonnet hf:meta-llama/Llama-3.3-70B-Instruct "Your prompt"
+synclaude --opus hf:moonshotai/Kimi-K2-Thinking "Your prompt"
+synclaude --sonnet hf:zai-org/GLM-4.6 "Your prompt"
 synclaude --haiku hf:qwen/Qwen2.5-Coder-32B-Instruct "Your prompt"
 synclaude --agent hf:zai-org/GLM-4.6 "Your prompt"
 ```
 
-Individual overrides take precedence over group overrides.
+Individual overrides take precedence over group overrides, which take precedence over cached/fallback defaults.
 
 ## How it works
 
@@ -132,10 +161,14 @@ Individual overrides take precedence over group overrides.
 - `CLAUDE_CODE_SUBAGENT_MODEL`: Model for sub-agent operations
 - `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`: Disabled because telemetry bad
 
+### Caching
+
+Model preferences are stored in `~/.config/synclaude/models.conf` (respects `XDG_CONFIG_HOME`). Use interactive mode (`synclaude i`) to save new defaults.
+
 After each run, it queries the Synthetic API to display:
 
 - Requests used during the session
-- Remaining quota (color-coded: green <33%, yellow 33-66%, red >66%)
+- Remaining quota (color-coded badge: green <33%, yellow 33-66%, red >66%)
 
 ## Contributions
 

completions/synclaude.fish 🔗

@@ -5,15 +5,17 @@
 # Inherit all claude completions
 complete -c synclaude -w claude
 
-# Interactive mode
-complete -c synclaude -n "not __fish_seen_subcommand_from i" -a i -d "Interactive model selection"
+# Subcommands
+complete -c synclaude -n "not __fish_seen_subcommand_from i u" -a i -d "Interactive model selection"
+complete -c synclaude -n "not __fish_seen_subcommand_from i u" -a u -d "Show current quota usage"
 
 # Group model overrides
-complete -c synclaude -s L -l large -r -d "Override Opus, Sonnet, and Sub-agent models"
+complete -c synclaude -s H -l heavy -r -d "Override Opus model"
+complete -c synclaude -s M -l medium -r -d "Override Sonnet and Sub-agent models"
 complete -c synclaude -s l -l light -r -d "Override Haiku model"
 
 # Specific model overrides
 complete -c synclaude -s o -l opus -r -d "Override Opus model"
 complete -c synclaude -s s -l sonnet -r -d "Override Sonnet model"
-complete -c synclaude -s H -l haiku -r -d "Override Haiku model"
+complete -c synclaude -s k -l haiku -r -d "Override Haiku model"
 complete -c synclaude -s a -l agent -r -d "Override Sub-agent model"

demo.tape 🔗

@@ -26,14 +26,20 @@ Enter Sleep 2s
 Sleep 750ms
 Enter Sleep 2s
 
-# Select heavy group
+# Select light group
+Down Sleep 250ms
+Down Sleep 500ms
 Space Sleep 750ms
 Enter Sleep 2s
 
 # Select the heavy model
-Type "mmm2" Sleep 1.25s
+Type "dsv31t" Sleep 1.25s
 Enter Sleep 2.25s
 
+# Decline saving as default
+Right Sleep 500ms
+Enter Sleep 2.2s
+
 # Send a message
 Type "Hi!" Sleep 1.25s
 Enter Sleep 4s

functions/synclaude.fish 🔗

@@ -2,43 +2,177 @@
 #
 # SPDX-License-Identifier: Unlicense
 
-function synclaude --wraps=claude --description "Run claude with Synthetic GLM-4.6 backend"
-    set -l default_model hf:zai-org/GLM-4.6
+# Cache file for model preferences
+set -g _synclaude_cache_file (set -q XDG_CONFIG_HOME; and echo "$XDG_CONFIG_HOME"; or echo "$HOME/.config")"/synclaude/models.conf"
+
+# Fallback defaults (used when no cache entry exists)
+set -g _synclaude_fallback_opus "hf:moonshotai/Kimi-K2-Thinking"
+set -g _synclaude_fallback_sonnet "hf:zai-org/GLM-4.6"
+set -g _synclaude_fallback_haiku "hf:deepseek-ai/DeepSeek-V3.1-Terminus"
+set -g _synclaude_fallback_agent "hf:zai-org/GLM-4.6"
+
+function _synclaude_cache_get --description "Get a cached value: _synclaude_cache_get slot"
+    set -l slot $argv[1]
+    set -l key "claude.$slot"
+
+    if not test -f "$_synclaude_cache_file"
+        return 1
+    end
+
+    set -l match (string match -r "^$key\\s*=\\s*(.+)" < "$_synclaude_cache_file")
+    if test -n "$match[2]"
+        string trim "$match[2]"
+        return 0
+    end
+    return 1
+end
+
+function _synclaude_cache_set --description "Set a cached value: _synclaude_cache_set slot value"
+    set -l slot $argv[1]
+    set -l value $argv[2]
+    set -l key "claude.$slot"
+
+    mkdir -p (dirname "$_synclaude_cache_file")
+
+    if not test -f "$_synclaude_cache_file"
+        echo "# synclaude model preferences" > "$_synclaude_cache_file"
+        echo "# Format: claude.slot = model_id" >> "$_synclaude_cache_file"
+        echo "" >> "$_synclaude_cache_file"
+    end
+
+    if string match -rq "^$key\\s*=" < "$_synclaude_cache_file"
+        set -l tmp (mktemp)
+        string replace -r "^$key\\s*=.*" "$key = $value" < "$_synclaude_cache_file" > "$tmp"
+        mv "$tmp" "$_synclaude_cache_file"
+    else
+        echo "$key = $value" >> "$_synclaude_cache_file"
+    end
+end
+
+function _synclaude_default --description "Get default model: _synclaude_default slot"
+    set -l slot $argv[1]
+    set -l cached (_synclaude_cache_get $slot)
+    if test $status -eq 0
+        echo $cached
+    else
+        set -l var_name _synclaude_fallback_$slot
+        echo $$var_name
+    end
+end
+
+function _synclaude_get_quota --description "Fetch quota from Synthetic API"
+    if not set -q SYNTHETIC_API_KEY
+        echo "Error: SYNTHETIC_API_KEY environment variable not set" >&2
+        return 1
+    end
+
+    set -l response (curl -s -f -H "Authorization: Bearer $SYNTHETIC_API_KEY" \
+        "https://api.synthetic.new/v2/quotas" 2>/dev/null)
+
+    if test $status -ne 0 -o -z "$response"
+        return 1
+    end
+
+    set -l requests (echo "$response" | jq -r '.subscription.requests // 0')
+    set -l limit (echo "$response" | jq -r '.subscription.limit // 0')
+
+    echo "$requests $limit"
+end
+
+function synclaude --wraps=claude --description "Run claude with Synthetic API backend"
+    # Handle usage subcommand
+    if test (count $argv) -gt 0 && test "$argv[1]" = "u"
+        set -l quota (_synclaude_get_quota)
+        if test $status -ne 0
+            echo "Error: Could not fetch quota" >&2
+            return 1
+        end
+        set -l requests (echo "$quota" | cut -d' ' -f1)
+        set -l limit (echo "$quota" | cut -d' ' -f2)
+        set -l remaining (math "$limit - $requests")
+        set -l percent_used (math -s0 "$requests * 100 / $limit")
+        printf "Usage: "
+        if test $percent_used -lt 33
+            set_color -b green black
+        else if test $percent_used -lt 67
+            set_color -b yellow black
+        else
+            set_color -b red black
+        end
+        printf " %s%% " $percent_used
+        set_color normal
+        printf " (%s/%s remaining)\n" $remaining $limit
+        return 0
+    end
 
     # Handle interactive mode
     if test (count $argv) -gt 0 && test "$argv[1]" = "i"
         set -e argv[1]  # Remove 'i' from args
-        
+
+        # Check for gum
+        if not command -q gum
+            echo "Error: gum is required for interactive mode. Install: https://github.com/charmbracelet/gum" >&2
+            return 1
+        end
+
         # Fetch available models
-        set -l models_json (gum spin --spinner dot --title "Fetching models..." -- curl -s -H "Authorization: Bearer $SYNTHETIC_API_KEY" https://api.synthetic.new/openai/v1/models)
+        set -l models_json (gum spin --spinner dot --title "Fetching models..." -- \
+            curl -s -H "Authorization: Bearer $SYNTHETIC_API_KEY" \
+            "https://api.synthetic.new/openai/v1/models")
         or return 1
+
         set -l model_names (echo $models_json | jq -r '.data[].name')
         or return 1
-        
+
+        # Get current defaults for display
+        set -l current_opus_id (_synclaude_default opus)
+        set -l current_sonnet_id (_synclaude_default sonnet)
+        set -l current_haiku_id (_synclaude_default haiku)
+        set -l current_agent_id (_synclaude_default agent)
+
         # Prompt for groups vs individual
-        set -l mode (gum choose --limit 1 --header "How do you want to select models?" "Groups" "Individual models")
+        set -l mode (gum choose --limit 1 --header "How do you want to select models?" \
+            "Groups" "Individual models")
         or return 1
-        
+
         # Build flags array
         set -l flags
-        
+
         if test "$mode" = "Groups"
             # Select which groups to override
-            set -l groups (gum choose --no-limit --header "Which group(s) do you want to override?" "Large (Opus, Sonnet, Sub-agent)" "Light (Haiku)")
+            set -l groups (gum choose --no-limit \
+                --header "Which group(s) do you want to override?" \
+                "Heavy (Opus)" "Medium (Sonnet, Sub-agent)" "Light (Haiku)")
             or return 1
-            
+
             for group in $groups
-                if test "$group" = "Large (Opus, Sonnet, Sub-agent)"
-                    set -l model_name (printf "%s\n" $model_names | gum filter --limit 1 --header "Select model for Large group" --placeholder "Filter models...")
+                if test "$group" = "Heavy (Opus)"
+                    set -l model_name (printf "%s\n" $model_names | \
+                        gum filter --limit 1 --header "Select model for Heavy group (opus: $current_opus_id)" \
+                        --placeholder "Filter models...")
                     or return 1
-                    set -l model_id (echo $models_json | jq -r --arg name "$model_name" '.data[] | select(.name == $name) | .id')
+                    set -l model_id (echo $models_json | \
+                        jq -r --arg name "$model_name" '.data[] | select(.name == $name) | .id')
                     if test -n "$model_id"
-                        set flags $flags --large=$model_id
+                        set flags $flags --heavy=$model_id
+                    end
+                else if test "$group" = "Medium (Sonnet, Sub-agent)"
+                    set -l model_name (printf "%s\n" $model_names | \
+                        gum filter --limit 1 --header "Select model for Medium group (sonnet: $current_sonnet_id, agent: $current_agent_id)" \
+                        --placeholder "Filter models...")
+                    or return 1
+                    set -l model_id (echo $models_json | \
+                        jq -r --arg name "$model_name" '.data[] | select(.name == $name) | .id')
+                    if test -n "$model_id"
+                        set flags $flags --medium=$model_id
                     end
                 else if test "$group" = "Light (Haiku)"
-                    set -l model_name (printf "%s\n" $model_names | gum filter --limit 1 --header "Select model for Light group" --placeholder "Filter models...")
+                    set -l model_name (printf "%s\n" $model_names | \
+                        gum filter --limit 1 --header "Select model for Light group (haiku: $current_haiku_id)" \
+                        --placeholder "Filter models...")
                     or return 1
-                    set -l model_id (echo $models_json | jq -r --arg name "$model_name" '.data[] | select(.name == $name) | .id')
+                    set -l model_id (echo $models_json | \
+                        jq -r --arg name "$model_name" '.data[] | select(.name == $name) | .id')
                     if test -n "$model_id"
                         set flags $flags --light=$model_id
                     end
@@ -46,115 +180,182 @@ function synclaude --wraps=claude --description "Run claude with Synthetic GLM-4
             end
         else
             # Select which individual models to override
-            set -l models (gum choose --no-limit --header "Which model(s) do you want to override?" "Opus" "Sonnet" "Haiku" "Sub-agent")
+            set -l models (gum choose --no-limit \
+                --header "Which model(s) do you want to override?" \
+                "Opus" "Sonnet" "Haiku" "Sub-agent")
             or return 1
-            
+
             for model_type in $models
                 switch $model_type
                     case "Opus"
-                        set -l model_name (printf "%s\n" $model_names | gum filter --limit 1 --header "Select Opus model" --placeholder "Filter models...")
+                        set -l model_name (printf "%s\n" $model_names | \
+                            gum filter --limit 1 --header "Select Opus model (current: $current_opus_id)" \
+                            --placeholder "Filter models...")
                         or return 1
-                        set -l model_id (echo $models_json | jq -r --arg name "$model_name" '.data[] | select(.name == $name) | .id')
+                        set -l model_id (echo $models_json | \
+                            jq -r --arg name "$model_name" '.data[] | select(.name == $name) | .id')
                         if test -n "$model_id"
                             set flags $flags --opus=$model_id
                         end
                     case "Sonnet"
-                        set -l model_name (printf "%s\n" $model_names | gum filter --limit 1 --header "Select Sonnet model" --placeholder "Filter models...")
+                        set -l model_name (printf "%s\n" $model_names | \
+                            gum filter --limit 1 --header "Select Sonnet model (current: $current_sonnet_id)" \
+                            --placeholder "Filter models...")
                         or return 1
-                        set -l model_id (echo $models_json | jq -r --arg name "$model_name" '.data[] | select(.name == $name) | .id')
+                        set -l model_id (echo $models_json | \
+                            jq -r --arg name "$model_name" '.data[] | select(.name == $name) | .id')
                         if test -n "$model_id"
                             set flags $flags --sonnet=$model_id
                         end
                     case "Haiku"
-                        set -l model_name (printf "%s\n" $model_names | gum filter --limit 1 --header "Select Haiku model" --placeholder "Filter models...")
+                        set -l model_name (printf "%s\n" $model_names | \
+                            gum filter --limit 1 --header "Select Haiku model (current: $current_haiku_id)" \
+                            --placeholder "Filter models...")
                         or return 1
-                        set -l model_id (echo $models_json | jq -r --arg name "$model_name" '.data[] | select(.name == $name) | .id')
+                        set -l model_id (echo $models_json | \
+                            jq -r --arg name "$model_name" '.data[] | select(.name == $name) | .id')
                         if test -n "$model_id"
                             set flags $flags --haiku=$model_id
                         end
                     case "Sub-agent"
-                        set -l model_name (printf "%s\n" $model_names | gum filter --limit 1 --header "Select Sub-agent model" --placeholder "Filter models...")
+                        set -l model_name (printf "%s\n" $model_names | \
+                            gum filter --limit 1 --header "Select Sub-agent model (current: $current_agent_id)" \
+                            --placeholder "Filter models...")
                         or return 1
-                        set -l model_id (echo $models_json | jq -r --arg name "$model_name" '.data[] | select(.name == $name) | .id')
+                        set -l model_id (echo $models_json | \
+                            jq -r --arg name "$model_name" '.data[] | select(.name == $name) | .id')
                         if test -n "$model_id"
                             set flags $flags --agent=$model_id
                         end
                 end
             end
         end
-        
+
+        # Offer to save as defaults
+        if test (count $flags) -gt 0
+            if gum confirm "Save as defaults?"
+                for flag in $flags
+                    set -l parts (string match -r -- '^--([^=]+)=(.+)$' $flag)
+                    if test -n "$parts[2]"
+                        set -l key $parts[2]
+                        set -l value $parts[3]
+                        switch $key
+                            case heavy
+                                _synclaude_cache_set opus $value
+                            case medium
+                                _synclaude_cache_set sonnet $value
+                                _synclaude_cache_set agent $value
+                            case light
+                                _synclaude_cache_set haiku $value
+                            case '*'
+                                _synclaude_cache_set $key $value
+                        end
+                    end
+                end
+            end
+        end
+
         # Recursively call synclaude with selected flags and remaining args
         synclaude $flags $argv
         return $status
     end
 
     # Parse arguments
-    # -L/--large: Sets Opus, Sonnet, and Sub-agent models
+    # -H/--heavy: Sets Opus model
+    # -M/--medium: Sets Sonnet and Sub-agent models
     # -l/--light: Sets Haiku model
-    # -o/--opus, -s/--sonnet, -H/--haiku, -a/--agent: Set specific models
+    # -o/--opus, -s/--sonnet, -k/--haiku, -a/--agent: Set specific models
     argparse --ignore-unknown \
-        'L/large=' 'l/light=' \
-        'o/opus=' 's/sonnet=' 'H/haiku=' 'a/agent=' -- $argv
-    or return
+        'H/heavy=' 'M/medium=' 'l/light=' \
+        'o/opus=' 's/sonnet=' 'k/haiku=' 'a/agent=' -- $argv
+    or return 1
 
-    set -lx ANTHROPIC_BASE_URL https://api.synthetic.new/anthropic
-    set -lx ANTHROPIC_AUTH_TOKEN $SYNTHETIC_API_KEY
-    
-    # Set defaults
-    set -lx ANTHROPIC_DEFAULT_OPUS_MODEL $default_model
-    set -lx ANTHROPIC_DEFAULT_SONNET_MODEL $default_model
-    set -lx ANTHROPIC_DEFAULT_HAIKU_MODEL $default_model
-    set -lx CLAUDE_CODE_SUBAGENT_MODEL $default_model
-    set -lx CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC 1
+    # Start with defaults (from cache or fallback)
+    set -l opus_model (_synclaude_default opus)
+    set -l sonnet_model (_synclaude_default sonnet)
+    set -l haiku_model (_synclaude_default haiku)
+    set -l subagent_model (_synclaude_default agent)
 
     # Apply group overrides
-    if set -q _flag_large
-        set ANTHROPIC_DEFAULT_OPUS_MODEL $_flag_large
-        set ANTHROPIC_DEFAULT_SONNET_MODEL $_flag_large
-        set CLAUDE_CODE_SUBAGENT_MODEL $_flag_large
+    if set -q _flag_heavy
+        set opus_model $_flag_heavy
+    end
+
+    if set -q _flag_medium
+        set sonnet_model $_flag_medium
+        set subagent_model $_flag_medium
     end
 
     if set -q _flag_light
-        set ANTHROPIC_DEFAULT_HAIKU_MODEL $_flag_light
+        set haiku_model $_flag_light
     end
 
-    # Apply specific overrides (taking precedence over groups)
-    if set -q _flag_opus; set ANTHROPIC_DEFAULT_OPUS_MODEL $_flag_opus; end
-    if set -q _flag_sonnet; set ANTHROPIC_DEFAULT_SONNET_MODEL $_flag_sonnet; end
-    if set -q _flag_haiku; set ANTHROPIC_DEFAULT_HAIKU_MODEL $_flag_haiku; end
-    if set -q _flag_agent; set CLAUDE_CODE_SUBAGENT_MODEL $_flag_agent; end
+    # Apply specific overrides (take precedence over groups)
+    if set -q _flag_opus
+        set opus_model $_flag_opus
+    end
+    if set -q _flag_sonnet
+        set sonnet_model $_flag_sonnet
+    end
+    if set -q _flag_haiku
+        set haiku_model $_flag_haiku
+    end
+    if set -q _flag_agent
+        set subagent_model $_flag_agent
+    end
 
-    # Fetch initial quota
-    set -l initial_response (gum spin --spinner dot --title "Checking quota..." -- curl -s -H "Authorization: Bearer $SYNTHETIC_API_KEY" https://api.synthetic.new/v2/quotas 2>/dev/null)
-    set -l initial_requests (echo $initial_response | jq -r '.subscription.requests // 0' 2>/dev/null)
-    test -n "$initial_requests" || set initial_requests 0
+    # Export environment variables for Claude Code
+    set -lx ANTHROPIC_BASE_URL "https://api.synthetic.new/anthropic"
+    set -lx ANTHROPIC_AUTH_TOKEN $SYNTHETIC_API_KEY
+    set -lx ANTHROPIC_DEFAULT_OPUS_MODEL $opus_model
+    set -lx ANTHROPIC_DEFAULT_SONNET_MODEL $sonnet_model
+    set -lx ANTHROPIC_DEFAULT_HAIKU_MODEL $haiku_model
+    set -lx CLAUDE_CODE_SUBAGENT_MODEL $subagent_model
+    set -lx CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC 1
+
+    # Fetch quota before execution
+    set -l quota_before (_synclaude_get_quota)
+    if test $status -ne 0
+        echo "Warning: Could not fetch quota before execution" >&2
+        set quota_before "0 0"
+    end
 
+    set -l requests_before (echo "$quota_before" | cut -d' ' -f1)
+    set -l limit (echo "$quota_before" | cut -d' ' -f2)
+
+    # Execute claude
     command claude $argv
+    set -l exit_status $status
 
-    # Fetch final quota
-    set -l final_response (gum spin --spinner dot --title "Fetching usage..." -- curl -s -H "Authorization: Bearer $SYNTHETIC_API_KEY" https://api.synthetic.new/v2/quotas 2>/dev/null)
-    set -l final_requests (echo $final_response | jq -r '.subscription.requests // 0' 2>/dev/null)
-    set -l limit (echo $final_response | jq -r '.subscription.limit // 0' 2>/dev/null)
-
-    # Calculate and display
-    if test -n "$limit" && test "$limit" -gt 0
-        set -l session_usage (math "$final_requests - $initial_requests")
-        set -l remaining (math "$limit - $final_requests")
-        set -l percent_used (math -s0 "($final_requests * 100) / $limit")
-
-        set -l quota_color green
-        if test "$percent_used" -gt 66
-            set quota_color red
-        else if test "$percent_used" -gt 33
-            set quota_color yellow
-        end
+    # Fetch quota after execution
+    set -l quota_after (_synclaude_get_quota)
+    if test $status -ne 0
+        echo "Warning: Could not fetch quota after execution" >&2
+        return $exit_status
+    end
 
-        echo
-        echo "Session usage: $session_usage requests"
-        echo -n "Remaining: "
-        set_color $quota_color
-        echo -n "$remaining/$limit"
-        set_color normal
-        echo " requests"
+    set -l requests_after (echo "$quota_after" | cut -d' ' -f1)
+
+    # Calculate session usage and remaining quota
+    set -l session_usage (math "$requests_after - $requests_before")
+    set -l remaining (math "$limit - $requests_after")
+    set -l percent_used (math -s0 "$requests_after * 100 / $limit")
+
+    # Display session usage and remaining quota with color
+    printf "\n"
+    printf "Session: %s requests\n" $session_usage
+    printf "Overall: "
+
+    if test $percent_used -lt 33
+        set_color -b green black
+    else if test $percent_used -lt 67
+        set_color -b yellow black
+    else
+        set_color -b red black
     end
+    printf " %s%% " $percent_used
+    set_color normal
+    printf " (%s/%s remaining)\n" $remaining $limit
+
+    return $exit_status
 end