Skip to content

Commit e663a61

Browse files
committed
inital commit
1 parent 10a5dd6 commit e663a61

35 files changed

+7191
-0
lines changed

.github/workflows/ci.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
test:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- uses: actions/setup-go@v5
19+
with:
20+
go-version: "1.26"
21+
22+
- name: Download dependencies
23+
run: go mod download
24+
25+
- name: Verify dependencies
26+
run: go mod verify
27+
28+
- name: Vet
29+
run: go vet ./...
30+
31+
- name: Build
32+
run: go build -o modelslab ./cmd/modelslab/
33+
34+
- name: Unit Tests
35+
run: go test ./internal/... -v -count=1
36+
37+
- name: Integration Tests (no auth)
38+
run: go test ./tests/ -v -count=1 -timeout 120s
39+
40+
build-matrix:
41+
runs-on: ubuntu-latest
42+
needs: test
43+
strategy:
44+
matrix:
45+
goos: [darwin, linux, windows]
46+
goarch: [amd64, arm64]
47+
exclude:
48+
- goos: windows
49+
goarch: arm64
50+
steps:
51+
- uses: actions/checkout@v4
52+
53+
- uses: actions/setup-go@v5
54+
with:
55+
go-version: "1.26"
56+
57+
- name: Build
58+
env:
59+
GOOS: ${{ matrix.goos }}
60+
GOARCH: ${{ matrix.goarch }}
61+
CGO_ENABLED: "0"
62+
run: |
63+
ext=""
64+
if [ "${{ matrix.goos }}" = "windows" ]; then ext=".exe"; fi
65+
go build -ldflags="-s -w" -o "modelslab-${{ matrix.goos }}-${{ matrix.goarch }}${ext}" ./cmd/modelslab/
66+
67+
- uses: actions/upload-artifact@v4
68+
with:
69+
name: modelslab-${{ matrix.goos }}-${{ matrix.goarch }}
70+
path: modelslab-*

.github/workflows/release.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- uses: actions/setup-go@v5
18+
with:
19+
go-version: "1.26"
20+
21+
- name: Unit Tests
22+
run: go test ./internal/... -count=1
23+
24+
release:
25+
runs-on: ubuntu-latest
26+
needs: test
27+
steps:
28+
- uses: actions/checkout@v4
29+
with:
30+
fetch-depth: 0
31+
32+
- uses: actions/setup-go@v5
33+
with:
34+
go-version: "1.26"
35+
36+
- name: Run GoReleaser
37+
uses: goreleaser/goreleaser-action@v6
38+
with:
39+
distribution: goreleaser
40+
version: "~> v2"
41+
args: release --clean
42+
env:
43+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44+
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
45+
SCOOP_BUCKET_GITHUB_TOKEN: ${{ secrets.SCOOP_BUCKET_GITHUB_TOKEN }}

.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Binaries
2+
modelslab
3+
modelslab.exe
4+
*.exe
5+
*.dll
6+
*.so
7+
*.dylib
8+
9+
# Test binary, built with `go test -c`
10+
*.test
11+
12+
# Output of the go coverage tool
13+
*.out
14+
15+
# Go workspace
16+
go.work
17+
go.work.sum
18+
19+
# GoReleaser
20+
dist/
21+
22+
# IDE
23+
.idea/
24+
.vscode/
25+
*.swp
26+
*.swo
27+
*~
28+
29+
# OS
30+
.DS_Store
31+
Thumbs.db
32+
33+
# Config (don't commit user config)
34+
.modelslab/
35+
36+
# Generated output
37+
generated/

.goreleaser.yml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
version: 2
2+
3+
project_name: modelslab
4+
5+
before:
6+
hooks:
7+
- go mod tidy
8+
9+
builds:
10+
- main: ./cmd/modelslab
11+
binary: modelslab
12+
env:
13+
- CGO_ENABLED=0
14+
goos:
15+
- darwin
16+
- linux
17+
- windows
18+
goarch:
19+
- amd64
20+
- arm64
21+
ldflags:
22+
- -s -w
23+
- -X main.version={{.Version}}
24+
- -X main.commit={{.Commit}}
25+
- -X main.date={{.Date}}
26+
27+
archives:
28+
- name_template: >-
29+
{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}
30+
formats:
31+
- tar.gz
32+
format_overrides:
33+
- goos: windows
34+
formats:
35+
- zip
36+
37+
checksum:
38+
name_template: "checksums.txt"
39+
40+
snapshot:
41+
version_template: "{{ incpatch .Version }}-next"
42+
43+
changelog:
44+
sort: asc
45+
filters:
46+
exclude:
47+
- "^docs:"
48+
- "^test:"
49+
- "^ci:"
50+
- "Merge pull request"
51+
52+
scoops:
53+
- repository:
54+
owner: ModelsLab
55+
name: scoop-bucket
56+
token: "{{ .Env.SCOOP_BUCKET_GITHUB_TOKEN }}"
57+
homepage: https://modelslab.com/cli
58+
description: "ModelsLab CLI — AI generation and account management from the terminal"
59+
license: MIT
60+
61+
nfpms:
62+
- formats:
63+
- deb
64+
- rpm
65+
homepage: https://modelslab.com/cli
66+
description: "ModelsLab CLI — AI generation and account management from the terminal"
67+
maintainer: "ModelsLab <support@modelslab.com>"
68+
license: MIT
69+
bindir: /usr/bin

AGENTS.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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

Comments
 (0)