|
| 1 | +# AGENTS.md — ModelsLab CLI |
| 2 | + |
| 3 | +> Guidelines for AI coding agents (Cursor, Copilot, Windsurf, Cline, etc.) working on this codebase. |
| 4 | +
|
| 5 | +## Project Overview |
| 6 | + |
| 7 | +Go CLI for the ModelsLab AI platform. 103 commands across 14 groups covering auth, profile, keys, models, generate (image/video/audio/3D/chat), billing, wallet, subscriptions, teams, usage, config, MCP, docs, and shell completions. ~10k lines across 26 Go files. |
| 8 | + |
| 9 | +**Stack**: Go 1.26 · Cobra + Viper · Charmbracelet (bubbletea, lipgloss, huh, glamour) · go-keyring · gojq · mcp-go · GoReleaser v2 |
| 10 | + |
| 11 | +## Build & Test |
| 12 | + |
| 13 | +```bash |
| 14 | +go build -o modelslab ./cmd/modelslab/ # Build binary |
| 15 | +go test ./internal/... -v # Unit tests (no server needed) |
| 16 | +go vet ./... # Lint |
| 17 | +goreleaser release --snapshot --clean # Full cross-platform build |
| 18 | +``` |
| 19 | + |
| 20 | +Integration tests require a local Laravel server: |
| 21 | +```bash |
| 22 | +cd ~/Documents/GitHub/modelslab-frontend-v2 && php artisan serve --port=8888 |
| 23 | +MODELSLAB_TEST_TOKEN="<token>" MODELSLAB_TEST_API_KEY="<key>" go test ./tests/ -v -base-url http://127.0.0.1:8888 |
| 24 | +``` |
| 25 | + |
| 26 | +## File Map |
| 27 | + |
| 28 | +| File | Purpose | |
| 29 | +|------|---------| |
| 30 | +| `cmd/modelslab/main.go` | Entry point, version ldflags | |
| 31 | +| `internal/api/client.go` | HTTP client — retry, rate limiting, dual auth (`DoControlPlane`, `DoGeneration`) | |
| 32 | +| `internal/auth/keyring.go` | OS keychain storage with JSON file fallback | |
| 33 | +| `internal/cmd/root.go` | Root command, global flags (`--output`, `--jq`, `--profile`, `--base-url`, `--api-key`) | |
| 34 | +| `internal/cmd/helpers.go` | `extractItems()`, `extractData()`, `firstNonNil()` — API response parsing | |
| 35 | +| `internal/cmd/auth.go` | 12 auth commands | |
| 36 | +| `internal/cmd/profile.go` | 6 profile commands | |
| 37 | +| `internal/cmd/keys.go` | 5 API key commands | |
| 38 | +| `internal/cmd/models.go` | 8 model discovery commands | |
| 39 | +| `internal/cmd/generate.go` | 20 generation commands + async polling + file download | |
| 40 | +| `internal/cmd/billing.go` | 10 billing commands + Stripe card tokenization | |
| 41 | +| `internal/cmd/wallet.go` | 10 wallet commands | |
| 42 | +| `internal/cmd/subscriptions.go` | 11 subscription commands | |
| 43 | +| `internal/cmd/teams.go` | 7 team commands | |
| 44 | +| `internal/cmd/usage.go` | 3 usage commands | |
| 45 | +| `internal/cmd/config.go` | 6 config/profile commands | |
| 46 | +| `internal/cmd/docs.go` | 2 docs commands | |
| 47 | +| `internal/cmd/completion.go` | Shell completions (bash/zsh/fish/powershell) | |
| 48 | +| `internal/cmd/mcp.go` | MCP serve + tools list | |
| 49 | +| `internal/config/config.go` | Viper config (`~/.config/modelslab/config.toml`) | |
| 50 | +| `internal/mcp/server.go` | MCP server with ~30 tools, stdio/SSE transports | |
| 51 | +| `internal/output/formatter.go` | JSON, table, jq, key-value output formatting | |
| 52 | +| `tests/integration_test.go` | 30 integration tests using subprocess execution | |
| 53 | + |
| 54 | +## Patterns You Must Follow |
| 55 | + |
| 56 | +### 1. Command Registration |
| 57 | +Every command file uses `init()` to register commands on the parent group: |
| 58 | +```go |
| 59 | +func init() { |
| 60 | + parentCmd.AddCommand(myNewCmd) |
| 61 | + rootCmd.AddCommand(parentCmd) // only for top-level groups |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +### 2. Dual Auth — Pick the Right Method |
| 66 | +- **Control plane** (`/api/agents/v1/*`): `client.DoControlPlane(method, path, body)` — uses Bearer token |
| 67 | +- **Generation** (`/api/*`): `client.DoGeneration(method, path, body)` — uses API key in request body |
| 68 | +- **Billing mutations**: `client.DoControlPlaneIdempotent(...)` — adds `Idempotency-Key` UUID header |
| 69 | + |
| 70 | +Never mix these up. Check existing commands in the same group for which method to use. |
| 71 | + |
| 72 | +### 3. API Response Parsing |
| 73 | +Always use the helpers from `internal/cmd/helpers.go`: |
| 74 | +```go |
| 75 | +items := extractItems(result) // for list endpoints — handles both {data:[...]} and {data:{items:[...]}} |
| 76 | +data := extractData(result) // for single-object endpoints — extracts data map |
| 77 | +name := firstNonNil(m, "model_name", "name", "title") // handles inconsistent field names |
| 78 | +``` |
| 79 | + |
| 80 | +### 4. Output Handling |
| 81 | +Every command must call `outputResult()` so `--output json` and `--jq` work: |
| 82 | +```go |
| 83 | +outputResult(cmd, result, func() { |
| 84 | + // human-readable output here (tables, key-value, etc.) |
| 85 | +}) |
| 86 | +``` |
| 87 | + |
| 88 | +### 5. Error Handling with Exit Codes |
| 89 | +The API client returns typed errors with semantic exit codes. Do not swallow errors — let them propagate: |
| 90 | +| Code | Meaning | |
| 91 | +|------|---------| |
| 92 | +| 0 | Success | |
| 93 | +| 1 | General error | |
| 94 | +| 2 | Usage error | |
| 95 | +| 3 | Auth error (401) | |
| 96 | +| 4 | Rate limited (429) | |
| 97 | +| 5 | Not found (404) | |
| 98 | +| 6 | Payment error | |
| 99 | +| 7 | Generation timeout | |
| 100 | +| 10 | Network error | |
| 101 | + |
| 102 | +### 6. Generation Commands |
| 103 | +Generation commands use `pollAndDownload()` for async workflows: |
| 104 | +1. POST to create generation → get `request_id` |
| 105 | +2. Poll with exponential backoff (1s→2s→4s→8s→10s cap, 5min timeout) |
| 106 | +3. Download output files to `./generated/` |
| 107 | +4. `--no-wait` flag skips polling |
| 108 | + |
| 109 | +### 7. Secrets |
| 110 | +- API keys and tokens are stored in OS keychain, not config files |
| 111 | +- `output.MaskSecret(s)` shows only last 4 chars — use this when displaying credentials |
| 112 | +- Stripe publishable key is embedded in `billing.go` — this is intentional (it's a client-side key) |
| 113 | +- Config key `api_key` is special-cased in `config.go` to store in keychain instead of config file |
| 114 | + |
| 115 | +## Do Not |
| 116 | + |
| 117 | +- **Do not add commands without registering them** in `init()` — they won't appear in the CLI |
| 118 | +- **Do not use `data["token"]` for login** — the API returns `access_token` (both are checked as fallback) |
| 119 | +- **Do not assume `data` is an array** — always use `extractItems()` which handles paginated responses |
| 120 | +- **Do not hardcode API paths** — follow the pattern: control plane uses `/api/agents/v1/...`, generation uses `/api/v6/...` |
| 121 | +- **Do not skip `go mod tidy`** after adding dependencies — CI will fail |
| 122 | +- **Do not use GoReleaser v1 syntax** — this project uses v2 (e.g., `formats:` list not `format:` string) |
| 123 | + |
| 124 | +## Adding a New Command (Step by Step) |
| 125 | + |
| 126 | +1. Identify the command group (auth, billing, generate, etc.) |
| 127 | +2. Open the corresponding `internal/cmd/<group>.go` |
| 128 | +3. Define the command using `&cobra.Command{...}` |
| 129 | +4. Add flags with `cmd.Flags().StringP(...)` etc. |
| 130 | +5. In the `RunE` function: |
| 131 | + - Call `getClient()` to get the API client |
| 132 | + - Call the appropriate `Do*()` method |
| 133 | + - Call `outputResult()` for output |
| 134 | +6. Register in `init()` on the parent command |
| 135 | +7. Add a test in the appropriate test file |
| 136 | + |
| 137 | +## Adding a New MCP Tool |
| 138 | + |
| 139 | +1. Edit `internal/mcp/server.go` |
| 140 | +2. Add a new `addTool()` call following existing patterns: |
| 141 | + ```go |
| 142 | + addTool("tool_name", "Description", map[string]interface{}{ |
| 143 | + "type": "object", |
| 144 | + "properties": map[string]interface{}{ |
| 145 | + "param": map[string]interface{}{"type": "string", "description": "..."}, |
| 146 | + }, |
| 147 | + }, func(args map[string]interface{}) (*mcp.CallToolResult, error) { |
| 148 | + // implementation |
| 149 | + }) |
| 150 | + ``` |
| 151 | + |
| 152 | +## Design Spec |
| 153 | + |
| 154 | +The authoritative spec for all 103 commands, API endpoints, and UX flows is in `cli.md` (1075 lines). Always consult it when adding features or fixing behavior. |
0 commit comments