Guidelines for AI coding agents (Cursor, Copilot, Windsurf, Cline, etc.) working on this codebase.
Go CLI for the ModelsLab AI platform. 105 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.
Stack: Go 1.26 · Cobra + Viper · Charmbracelet (bubbletea, lipgloss, huh, glamour) · go-keyring · gojq · mcp-go · GoReleaser v2
go build -o modelslab ./cmd/modelslab/ # Build binary
go test ./internal/... -v # Unit tests (no server needed)
go vet ./... # Lint
goreleaser release --snapshot --clean # Full cross-platform buildIntegration tests require a local Laravel server:
cd ~/Documents/GitHub/modelslab-frontend-v2 && php artisan serve --port=8888
MODELSLAB_TEST_TOKEN="<token>" MODELSLAB_TEST_API_KEY="<key>" go test ./tests/ -v -base-url http://127.0.0.1:8888| File | Purpose |
|---|---|
cmd/modelslab/main.go |
Entry point, version ldflags |
internal/api/client.go |
HTTP client — retry, rate limiting, dual auth (DoControlPlane, DoGeneration) |
internal/auth/keyring.go |
OS keychain storage with JSON file fallback |
internal/cmd/root.go |
Root command, global flags (--output, --jq, --profile, --base-url, --api-key) |
internal/cmd/helpers.go |
extractItems(), extractData(), firstNonNil() — API response parsing |
internal/cmd/auth.go |
12 auth commands |
internal/cmd/profile.go |
6 profile commands |
internal/cmd/keys.go |
5 API key commands |
internal/cmd/models.go |
8 model discovery commands |
internal/cmd/generate.go |
20 generation commands + async polling + file download |
internal/cmd/billing.go |
13 billing commands + Stripe card tokenization + payment links + setup intents |
internal/cmd/wallet.go |
10 wallet commands |
internal/cmd/subscriptions.go |
10 subscription commands |
internal/cmd/teams.go |
7 team commands |
internal/cmd/usage.go |
3 usage commands |
internal/cmd/config.go |
6 config/profile commands |
internal/cmd/docs.go |
2 docs commands |
internal/cmd/completion.go |
Shell completions (bash/zsh/fish/powershell) |
internal/cmd/mcp.go |
MCP serve + tools list |
internal/config/config.go |
Viper config (~/.config/modelslab/config.toml) |
internal/mcp/server.go |
MCP server with ~30 tools, stdio/SSE transports |
internal/output/formatter.go |
JSON, table, jq, key-value output formatting |
tests/integration_test.go |
30 integration tests using subprocess execution |
Every command file uses init() to register commands on the parent group:
func init() {
parentCmd.AddCommand(myNewCmd)
rootCmd.AddCommand(parentCmd) // only for top-level groups
}- Control plane (
/api/agents/v1/*):client.DoControlPlane(method, path, body)— uses Bearer token - Generation (
/api/*):client.DoGeneration(method, path, body)— uses API key in request body - Billing mutations:
client.DoControlPlaneIdempotent(...)— addsIdempotency-KeyUUID header
Never mix these up. Check existing commands in the same group for which method to use.
Always use the helpers from internal/cmd/helpers.go:
items := extractItems(result) // for list endpoints — handles both {data:[...]} and {data:{items:[...]}}
data := extractData(result) // for single-object endpoints — extracts data map
name := firstNonNil(m, "model_name", "name", "title") // handles inconsistent field namesEvery command must call outputResult() so --output json and --jq work:
outputResult(cmd, result, func() {
// human-readable output here (tables, key-value, etc.)
})The API client returns typed errors with semantic exit codes. Do not swallow errors — let them propagate:
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error |
| 2 | Usage error |
| 3 | Auth error (401) |
| 4 | Rate limited (429) |
| 5 | Not found (404) |
| 6 | Payment error |
| 7 | Generation timeout |
| 10 | Network error |
Generation commands use pollAndDownload() for async workflows:
- POST to create generation → get
request_id - Poll with exponential backoff (1s→2s→4s→8s→10s cap, 5min timeout)
- Download output files to
./generated/ --no-waitflag skips polling
- API keys and tokens are stored in OS keychain, not config files
output.MaskSecret(s)shows only last 4 chars — use this when displaying credentials- Stripe publishable key is embedded in
billing.go— this is intentional (it's a client-side key) - Config key
api_keyis special-cased inconfig.goto store in keychain instead of config file
- Do not add commands without registering them in
init()— they won't appear in the CLI - Do not use
data["token"]for login — the API returnsaccess_token(both are checked as fallback) - Do not assume
datais an array — always useextractItems()which handles paginated responses - Do not hardcode API paths — follow the pattern: control plane uses
/api/agents/v1/..., generation uses/api/v6/... - Do not skip
go mod tidyafter adding dependencies — CI will fail - Do not use GoReleaser v1 syntax — this project uses v2 (e.g.,
formats:list notformat:string)
- Identify the command group (auth, billing, generate, etc.)
- Open the corresponding
internal/cmd/<group>.go - Define the command using
&cobra.Command{...} - Add flags with
cmd.Flags().StringP(...)etc. - In the
RunEfunction:- Call
getClient()to get the API client - Call the appropriate
Do*()method - Call
outputResult()for output
- Call
- Register in
init()on the parent command - Add a test in the appropriate test file
- Edit
internal/mcp/server.go - Add a new
addTool()call following existing patterns:addTool("tool_name", "Description", map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "param": map[string]interface{}{"type": "string", "description": "..."}, }, }, func(args map[string]interface{}) (*mcp.CallToolResult, error) { // implementation })
The authoritative spec for all 102 commands, API endpoints, and UX flows is in cli.md. Always consult it when adding features or fixing behavior.