1<!--
2SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
3
4SPDX-License-Identifier: Unlicense
5-->
6
7# AGENTS.md - Developer Guide for synu
8
9This document helps AI agents work effectively with the synu codebase.
10
11## Project Overview
12
13**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:
14
15- Transparent quota tracking before/after agent execution
16- Agent-specific configuration system (e.g., model routing for Claude Code)
17- Interactive model selection with `gum`
18- Passthrough mode for agents without special configuration
19
20### Architecture
21
22```
23synu (main wrapper)
24 ├─ _synu_get_quota (API quota fetching)
25 └─ _synu_agents/ (agent-specific configuration)
26 └─ claude.fish (Claude Code model configuration)
27```
28
29**Control Flow:**
301. Parse arguments (check for interactive mode, agent name)
312. Load agent definition from `functions/_synu_agents/<agent>.fish` if it exists
323. Parse agent-specific flags using `argparse` with `--ignore-unknown` for passthrough
334. Configure agent environment (if agent has `_configure` function)
345. Fetch initial quota from Synthetic API
356. Execute agent with remaining arguments
367. Fetch final quota and calculate/display session usage
37
38**Data Flow:**
39- Quota API → `_synu_get_quota` → space-separated "requests limit" string
40- Agent flags → `argparse` → `_flag_*` variables → agent `_configure` function
41- Interactive mode → `gum` + Synthetic API → model IDs → recursive `synu` call with flags
42
43## File Structure
44
45```
46├── functions/
47│ ├── synu.fish # Main wrapper function
48│ ├── _synu_get_quota.fish # Private: Fetch quota from API
49│ └── _synu_agents/
50│ └── claude.fish # Claude Code agent configuration
51├── completions/
52│ └── synu.fish # Shell completions
53├── crush.json # LSP configuration (fish-lsp)
54├── README.md # User documentation
55└── .gitignore # Ignore agent working directories
56```
57
58## Essential Commands
59
60This is a Fish shell library with no build system. Key commands:
61
62### Testing Manually
63
64```fish
65# Source the function (from repo root)
66source functions/synu.fish
67source functions/_synu_get_quota.fish
68
69# Check quota only
70synu
71
72# Test with an agent
73synu claude "What does this function do?"
74
75# Test interactive mode (requires gum)
76synu i claude "prompt"
77
78# Test with model override flags
79synu claude --opus hf:some/model "prompt"
80synu claude --large hf:some/model "prompt"
81```
82
83### Installation Testing
84
85```fish
86# Install via fundle (in a clean fish instance)
87fundle plugin 'synu' --url 'https://git.secluded.site/amolith/llm-projects'
88fundle install
89fundle init
90```
91
92### Checking for Syntax Errors
93
94```fish
95# Validate Fish syntax
96fish -n functions/synu.fish
97fish -n functions/_synu_get_quota.fish
98fish -n functions/_synu_agents/claude.fish
99fish -n completions/synu.fish
100```
101
102### LSP Integration
103
104The project includes `crush.json` configuring `fish-lsp` for Fish language support. The LSP should automatically provide diagnostics when editing `.fish` files.
105
106## Code Conventions
107
108### Fish Shell Style
109
110- **Indentation**: 4 spaces (no tabs)
111- **Variable naming**: `snake_case` with descriptive names
112 - Private/local vars: `set -l var_name`
113 - Global vars: `set -g var_name` (agent defaults)
114 - Exported vars: `set -gx VAR_NAME` (environment)
115- **Function naming**:
116 - Public: `synu`
117 - Private/helper: `_synu_*`
118 - Agent-specific: `_synu_agent_<name>_<action>`
119- **Comments**: Use `#` for explanatory comments focusing on *why* not *what*
120- **Error handling**: Check `$status` after critical operations, return non-zero on errors
121- **Error output**: Use `>&2` for all error messages
122- **SPDX headers**: All files must include SPDX copyright and license headers
123
124### Agent Configuration Pattern
125
126Each agent definition in `functions/_synu_agents/<agent>.fish` can provide four functions:
127
1281. **`_synu_agent_<name>_flags`** - Returns argparse flag specification (one per line)
129 ```fish
130 function _synu_agent_myagent_flags
131 echo "L/large="
132 echo "o/option="
133 end
134 ```
135
1362. **`_synu_agent_<name>_configure`** - Sets environment variables before execution
137 ```fish
138 function _synu_agent_myagent_configure
139 argparse 'L/large=' 'o/option=' -- $argv
140 or return 1
141
142 # Configure based on flags
143 if set -q _flag_large
144 set -gx AGENT_MODEL $_flag_large
145 end
146 end
147 ```
148
1493. **`_synu_agent_<name>_env_vars`** - Returns list of environment variables to clean up after execution
150 ```fish
151 function _synu_agent_myagent_env_vars
152 echo AGENT_MODEL
153 echo AGENT_CONFIG
154 end
155 ```
156
1574. **`_synu_agent_<name>_interactive`** - Implements interactive model selection
158 ```fish
159 function _synu_agent_myagent_interactive
160 # Use gum to get user input
161 set -l selection (gum choose "option1" "option2")
162 or return 1
163
164 # Return flags to pass to main synu call
165 echo --option=$selection
166 end
167 ```
168
169**Important patterns:**
170- Agents without definitions work as passthrough (no special config)
171- Use `argparse --ignore-unknown` in main wrapper so agent-native flags pass through
172- Interactive functions return flags that trigger recursive `synu` call
173- Configure functions receive flags already parsed from `_flag_*` variables
174- Environment variables listed in `_env_vars` are automatically unset after agent exits
175
176### API Integration
177
178**Synthetic API endpoints:**
179- Quota: `GET https://api.synthetic.new/v2/quotas`
180- Models: `GET https://api.synthetic.new/openai/v1/models`
181- Claude routing: `https://api.synthetic.new/anthropic`
182
183**Authentication**: Bearer token via `Authorization: Bearer $SYNTHETIC_API_KEY`
184
185**Response parsing**: Use `jq` with fallbacks (`// 0`) for missing fields
186
187**Error handling**: Check `curl` exit status and response emptiness before parsing
188
189## Important Gotchas
190
191### Argument Passing and argparse
192
1931. **Passthrough requires `--ignore-unknown`**: When parsing agent flags, use `argparse --ignore-unknown` so agent-native flags aren't consumed by synu's parser.
194
1952. **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.
196
1973. **Preserve argument order**: When executing the agent with `command $agent $agent_args`, all original arguments (except synu-specific flags) must pass through unchanged.
198
199### Quota Tracking Edge Cases
200
2011. **API failures are non-fatal**: If quota fetch fails before/after execution, warn but continue. The agent should still run.
202
2032. **Default to zeros on failure**: If pre-execution quota fetch fails, use `"0 0"` to avoid math errors in post-execution display.
204
2053. **Exit status preservation**: Always return the agent's exit status (`$exit_status`), not the quota fetch status.
206
207### Environment Variable Scoping
208
209- Use `set -gx` for environment variables that need to be visible to child processes (the actual agent binary)
210- Agent defaults use `set -g` (global but not exported)
211- Local calculations use `set -l`
212
213### Interactive Mode Flow
214
215Interactive mode (`synu i <agent> [args...]`) works by:
2161. Calling agent's `_interactive` function to get flags
2172. Recursively calling `synu <agent> <flags> [args...]` (non-interactive)
2183. The recursive call then follows normal path with pre-parsed flags
219
220This means the `_configure` function must handle both direct flag input and flags from interactive mode identically.
221
222### Fish-specific Considerations
223
224- **Status checks**: Always use `test $status -ne 0` immediately after command, as any subsequent command overwrites `$status`
225- **Array slicing**: Use `$argv[2..-1]` syntax (1-indexed, inclusive)
226- **Command substitution**: Use `(command)` not `$(command)`
227- **String operations**: Use `string` builtin (`string split`, `string match`)
228- **No `local` keyword**: Use `set -l` for local scope
229
230## Testing Approach
231
232This project has no automated test suite. Testing is manual:
233
2341. **Syntax validation**: Run `fish -n` on all `.fish` files
2352. **Quota tracking**: Run `synu` alone, check quota display with color
2363. **Passthrough**: Test with unknown agent (`synu echo "hello"`)
2374. **Agent config**: Test Claude with/without flags
2385. **Interactive mode**: Test `synu i claude` with gum installed
2396. **Error cases**: Test without `SYNTHETIC_API_KEY`, with invalid API key
240
241**When making changes:**
242- Validate syntax with `fish -n`
243- Source the function and test manually with different argument combinations
244- Check that exit status is preserved
245- Verify quota display shows correct before/after values
246
247## Common Tasks
248
249### Adding a New Agent Configuration
250
2511. Create `functions/_synu_agents/<agent>.fish`
2522. Add SPDX header
2533. Define `_synu_agent_<agent>_flags` (return argparse flag specs)
2544. Define `_synu_agent_<agent>_configure` (set environment variables)
2555. Optionally define `_synu_agent_<agent>_interactive` (for `synu i <agent>`)
2566. Update `completions/synu.fish` to add the new agent to suggestions
2577. Test manually
258
259### Modifying Quota Display
260
261Quota display logic is in two places:
262- **No args case**: lines 6-28 of `synu.fish`
263- **Post-execution**: lines 128-166 of `synu.fish`
264
265Both use the same color thresholds:
266- Green: < 33% used
267- Yellow: 33-66% used
268- Red: > 66% used
269
270### Debugging API Issues
271
272```fish
273# Test quota fetch directly
274source functions/_synu_get_quota.fish
275set -gx SYNTHETIC_API_KEY your_key_here
276_synu_get_quota
277# Should output: "requests_used limit"
278
279# Test with curl directly
280curl -s -f -H "Authorization: Bearer $SYNTHETIC_API_KEY" \
281 "https://api.synthetic.new/v2/quotas" | jq .
282```
283
284### Understanding Model Configuration Flow (Claude)
285
2861. User runs: `synu claude --large hf:some/model "prompt"`
2872. `synu.fish` loads `functions/_synu_agents/claude.fish`
2883. Calls `_synu_agent_claude_flags` to get flag spec: `L/large=`, `o/opus=`, etc.
2894. Parses with `argparse --ignore-unknown` → sets `_flag_large`
2905. Rebuilds flags for configure: `--large=hf:some/model`
2916. Calls `_synu_agent_claude_configure --large=hf:some/model`
2927. Configure sets `ANTHROPIC_DEFAULT_OPUS_MODEL`, `ANTHROPIC_DEFAULT_SONNET_MODEL`, etc.
2938. Executes `command claude "prompt"` with those environment variables
2949. Claude Code sees Synthetic models via env vars and uses them
295
296## Commit Conventions
297
298Use **Conventional Commits** with type and scope:
299
300**Types:** `feat`, `fix`, `refactor`, `docs`, `chore`, `test`
301
302**Scopes:**
303- `synu` - main wrapper function
304- `quota` - quota fetching/display
305- `agents/<name>` - specific agent configuration (e.g., `agents/claude`)
306- `completions` - shell completions
307- `docs` - documentation
308
309**Examples:**
310```
311feat(synu): add support for model override flags
312fix(quota): handle API errors gracefully
313feat(agents/claude): add interactive model selection
314docs(readme): document interactive mode
315```
316
317**Trailers:**
318- Use `References: HASH` for related commits/bugs
319- Always include `Assisted-by: [Model Name] via [Tool Name]` when AI-assisted
320
321## Project-Specific Context
322
323### Why Fish Shell?
324
325This is Amolith's personal project and Fish is their shell of choice. Fish provides:
326- Clean syntax without bashisms
327- Better error handling than POSIX shells
328- Native arrays and proper scoping
329- Built-in string manipulation
330
331### Why Synthetic API?
332
333Synthetic provides a unified API for multiple LLM providers with quota tracking. This wrapper makes quota visibility automatic and consistent across different agent tools.
334
335### Design Philosophy
336
337- **Minimal interference**: Pass through as much as possible to the underlying agent
338- **Graceful degradation**: If quota tracking fails, still run the agent
339- **Extensible**: Easy to add new agent configurations without modifying core wrapper
340- **Interactive when helpful**: Support both CLI flags and TUI selection
341
342### Known Agents
343
344Suggested in completions (may or may not have specific configurations):
345- `claude` - Claude Code (has configuration)
346- `crush` - Crush agent (passthrough)
347- `amp` - Amp agent (passthrough)
348- `octo` - Octo agent (passthrough)
349- `codex` - Codex agent (passthrough)
350
351Any command can be wrapped, these are just suggestions for completion.
352
353## Future Considerations
354
355Potential enhancements (not currently implemented):
356- Cache quota between rapid calls to reduce API hits
357- Support for other quota tracking APIs besides Synthetic
358- Agent-specific usage tracking/history
359- Budget warnings/limits
360- More agent configurations (crush, amp, etc.)
361
362When implementing features, maintain the core principles:
363- Don't break passthrough mode
364- Keep it simple and transparent
365- Graceful degradation
366- Follow Fish idioms