<!--
SPDX-FileCopyrightText: Amolith <amolith@secluded.site>

SPDX-License-Identifier: Unlicense
-->

# AGENTS.md - Developer Guide for synu

This document helps AI agents work effectively with the synu codebase.

## Project Overview

**synu** is a universal wrapper for AI agents that tracks quota usage for [Synthetic](https://synthetic.new) API calls. It's written in Fish shell and provides:

- Transparent quota tracking before/after agent execution
- Agent-specific configuration system (e.g., model routing for Claude Code)
- Interactive model selection with `gum`
- Passthrough mode for agents without special configuration

### Architecture

```
synu (main wrapper)
  ├─ _synu_get_quota (API quota fetching)
  └─ _synu_agents/ (agent-specific configuration)
      └─ claude.fish (Claude Code model configuration)
```

**Control Flow:**
1. Parse arguments (check for interactive mode, agent name)
2. Load agent definition from `functions/_synu_agents/<agent>.fish` if it exists
3. Parse agent-specific flags using `argparse` with `--ignore-unknown` for passthrough
4. Configure agent environment (if agent has `_configure` function)
5. Fetch initial quota from Synthetic API
6. Execute agent with remaining arguments
7. Fetch final quota and calculate/display session usage

**Data Flow:**
- Quota API → `_synu_get_quota` → space-separated "requests limit" string
- Agent flags → `argparse` → `_flag_*` variables → agent `_configure` function
- Interactive mode → `gum` + Synthetic API → model IDs → recursive `synu` call with flags

## File Structure

```
├── functions/
│   ├── synu.fish              # Main wrapper function
│   ├── _synu_get_quota.fish   # Private: Fetch quota from API
│   └── _synu_agents/
│       └── claude.fish        # Claude Code agent configuration
├── completions/
│   └── synu.fish              # Shell completions
├── crush.json                 # LSP configuration (fish-lsp)
├── README.md                  # User documentation
└── .gitignore                 # Ignore agent working directories
```

## Essential Commands

This is a Fish shell library with no build system. Key commands:

### Testing Manually

```fish
# Source the function (from repo root)
source functions/synu.fish
source functions/_synu_get_quota.fish

# Check quota only
synu

# Test with an agent
synu claude "What does this function do?"

# Test interactive mode (requires gum)
synu i claude "prompt"

# Test with model override flags
synu claude --opus hf:some/model "prompt"
synu claude --large hf:some/model "prompt"
```

### Installation Testing

```fish
# Install via fundle (in a clean fish instance)
fundle plugin 'synu' --url 'https://git.secluded.site/amolith/llm-projects'
fundle install
fundle init
```

### Checking for Syntax Errors

```fish
# Validate Fish syntax
fish -n functions/synu.fish
fish -n functions/_synu_get_quota.fish
fish -n functions/_synu_agents/claude.fish
fish -n completions/synu.fish
```

### LSP Integration

The project includes `crush.json` configuring `fish-lsp` for Fish language support. The LSP should automatically provide diagnostics when editing `.fish` files.

## Code Conventions

### Fish Shell Style

- **Indentation**: 4 spaces (no tabs)
- **Variable naming**: `snake_case` with descriptive names
  - Private/local vars: `set -l var_name`
  - Global vars: `set -g var_name` (agent defaults)
  - Exported vars: `set -gx VAR_NAME` (environment)
- **Function naming**:
  - Public: `synu`
  - Private/helper: `_synu_*`
  - Agent-specific: `_synu_agent_<name>_<action>`
- **Comments**: Use `#` for explanatory comments focusing on *why* not *what*
- **Error handling**: Check `$status` after critical operations, return non-zero on errors
- **Error output**: Use `>&2` for all error messages
- **SPDX headers**: All files must include SPDX copyright and license headers

### Agent Configuration Pattern

Each agent definition in `functions/_synu_agents/<agent>.fish` can provide four functions:

1. **`_synu_agent_<name>_flags`** - Returns argparse flag specification (one per line)
   ```fish
   function _synu_agent_myagent_flags
       echo "L/large="
       echo "o/option="
   end
   ```

2. **`_synu_agent_<name>_configure`** - Sets environment variables before execution
   ```fish
   function _synu_agent_myagent_configure
       argparse 'L/large=' 'o/option=' -- $argv
       or return 1
       
       # Configure based on flags
       if set -q _flag_large
           set -gx AGENT_MODEL $_flag_large
       end
   end
   ```

3. **`_synu_agent_<name>_env_vars`** - Returns list of environment variables to clean up after execution
   ```fish
   function _synu_agent_myagent_env_vars
       echo AGENT_MODEL
       echo AGENT_CONFIG
   end
   ```

4. **`_synu_agent_<name>_interactive`** - Implements interactive model selection
   ```fish
   function _synu_agent_myagent_interactive
       # Use gum to get user input
       set -l selection (gum choose "option1" "option2")
       or return 1
       
       # Return flags to pass to main synu call
       echo --option=$selection
   end
   ```

**Important patterns:**
- Agents without definitions work as passthrough (no special config)
- Use `argparse --ignore-unknown` in main wrapper so agent-native flags pass through
- Interactive functions return flags that trigger recursive `synu` call
- Configure functions receive flags already parsed from `_flag_*` variables
- Environment variables listed in `_env_vars` are automatically unset after agent exits

### API Integration

**Synthetic API endpoints:**
- Quota: `GET https://api.synthetic.new/v2/quotas`
- Models: `GET https://api.synthetic.new/openai/v1/models`
- Claude routing: `https://api.synthetic.new/anthropic`

**Authentication**: Bearer token via `Authorization: Bearer $SYNTHETIC_API_KEY`

**Response parsing**: Use `jq` with fallbacks (`// 0`) for missing fields

**Error handling**: Check `curl` exit status and response emptiness before parsing

## Important Gotchas

### Argument Passing and argparse

1. **Passthrough requires `--ignore-unknown`**: When parsing agent flags, use `argparse --ignore-unknown` so agent-native flags aren't consumed by synu's parser.

2. **Flag variables after argparse**: After `argparse`, the `$argv` variable contains only non-flag arguments. Rebuild flag arguments from `_flag_*` variables to pass to configure function.

3. **Preserve argument order**: When executing the agent with `command $agent $agent_args`, all original arguments (except synu-specific flags) must pass through unchanged.

### Quota Tracking Edge Cases

1. **API failures are non-fatal**: If quota fetch fails before/after execution, warn but continue. The agent should still run.

2. **Default to zeros on failure**: If pre-execution quota fetch fails, use `"0 0"` to avoid math errors in post-execution display.

3. **Exit status preservation**: Always return the agent's exit status (`$exit_status`), not the quota fetch status.

### Environment Variable Scoping

- Use `set -gx` for environment variables that need to be visible to child processes (the actual agent binary)
- Agent defaults use `set -g` (global but not exported)
- Local calculations use `set -l`

### Interactive Mode Flow

Interactive mode (`synu i <agent> [args...]`) works by:
1. Calling agent's `_interactive` function to get flags
2. Recursively calling `synu <agent> <flags> [args...]` (non-interactive)
3. The recursive call then follows normal path with pre-parsed flags

This means the `_configure` function must handle both direct flag input and flags from interactive mode identically.

### Fish-specific Considerations

- **Status checks**: Always use `test $status -ne 0` immediately after command, as any subsequent command overwrites `$status`
- **Array slicing**: Use `$argv[2..-1]` syntax (1-indexed, inclusive)
- **Command substitution**: Use `(command)` not `$(command)`
- **String operations**: Use `string` builtin (`string split`, `string match`)
- **No `local` keyword**: Use `set -l` for local scope

## Testing Approach

This project has no automated test suite. Testing is manual:

1. **Syntax validation**: Run `fish -n` on all `.fish` files
2. **Quota tracking**: Run `synu` alone, check quota display with color
3. **Passthrough**: Test with unknown agent (`synu echo "hello"`)
4. **Agent config**: Test Claude with/without flags
5. **Interactive mode**: Test `synu i claude` with gum installed
6. **Error cases**: Test without `SYNTHETIC_API_KEY`, with invalid API key

**When making changes:**
- Validate syntax with `fish -n`
- Source the function and test manually with different argument combinations
- Check that exit status is preserved
- Verify quota display shows correct before/after values

## Common Tasks

### Adding a New Agent Configuration

1. Create `functions/_synu_agents/<agent>.fish`
2. Add SPDX header
3. Define `_synu_agent_<agent>_flags` (return argparse flag specs)
4. Define `_synu_agent_<agent>_configure` (set environment variables)
5. Optionally define `_synu_agent_<agent>_interactive` (for `synu i <agent>`)
6. Update `completions/synu.fish` to add the new agent to suggestions
7. Test manually

### Modifying Quota Display

Quota display logic is in two places:
- **No args case**: lines 6-28 of `synu.fish`
- **Post-execution**: lines 128-166 of `synu.fish`

Both use the same color thresholds:
- Green: < 33% used
- Yellow: 33-66% used
- Red: > 66% used

### Debugging API Issues

```fish
# Test quota fetch directly
source functions/_synu_get_quota.fish
set -gx SYNTHETIC_API_KEY your_key_here
_synu_get_quota
# Should output: "requests_used limit"

# Test with curl directly
curl -s -f -H "Authorization: Bearer $SYNTHETIC_API_KEY" \
    "https://api.synthetic.new/v2/quotas" | jq .
```

### Understanding Model Configuration Flow (Claude)

1. User runs: `synu claude --large hf:some/model "prompt"`
2. `synu.fish` loads `functions/_synu_agents/claude.fish`
3. Calls `_synu_agent_claude_flags` to get flag spec: `L/large=`, `o/opus=`, etc.
4. Parses with `argparse --ignore-unknown` → sets `_flag_large`
5. Rebuilds flags for configure: `--large=hf:some/model`
6. Calls `_synu_agent_claude_configure --large=hf:some/model`
7. Configure sets `ANTHROPIC_DEFAULT_OPUS_MODEL`, `ANTHROPIC_DEFAULT_SONNET_MODEL`, etc.
8. Executes `command claude "prompt"` with those environment variables
9. Claude Code sees Synthetic models via env vars and uses them

## Commit Conventions

Use **Conventional Commits** with type and scope:

**Types:** `feat`, `fix`, `refactor`, `docs`, `chore`, `test`

**Scopes:**
- `synu` - main wrapper function
- `quota` - quota fetching/display
- `agents/<name>` - specific agent configuration (e.g., `agents/claude`)
- `completions` - shell completions
- `docs` - documentation

**Examples:**
```
feat(synu): add support for model override flags
fix(quota): handle API errors gracefully
feat(agents/claude): add interactive model selection
docs(readme): document interactive mode
```

**Trailers:**
- Use `References: HASH` for related commits/bugs
- Always include `Assisted-by: [Model Name] via [Tool Name]` when AI-assisted

## Project-Specific Context

### Why Fish Shell?

This is Amolith's personal project and Fish is their shell of choice. Fish provides:
- Clean syntax without bashisms
- Better error handling than POSIX shells
- Native arrays and proper scoping
- Built-in string manipulation

### Why Synthetic API?

Synthetic provides a unified API for multiple LLM providers with quota tracking. This wrapper makes quota visibility automatic and consistent across different agent tools.

### Design Philosophy

- **Minimal interference**: Pass through as much as possible to the underlying agent
- **Graceful degradation**: If quota tracking fails, still run the agent
- **Extensible**: Easy to add new agent configurations without modifying core wrapper
- **Interactive when helpful**: Support both CLI flags and TUI selection

### Known Agents

Suggested in completions (may or may not have specific configurations):
- `claude` - Claude Code (has configuration)
- `crush` - Crush agent (passthrough)
- `amp` - Amp agent (passthrough)
- `octo` - Octo agent (passthrough)
- `codex` - Codex agent (passthrough)

Any command can be wrapped, these are just suggestions for completion.

## Future Considerations

Potential enhancements (not currently implemented):
- Cache quota between rapid calls to reduce API hits
- Support for other quota tracking APIs besides Synthetic
- Agent-specific usage tracking/history
- Budget warnings/limits
- More agent configurations (crush, amp, etc.)

When implementing features, maintain the core principles:
- Don't break passthrough mode
- Keep it simple and transparent
- Graceful degradation
- Follow Fish idioms
