|
| 1 | +# AI Agent Development Guide |
| 2 | + |
| 3 | +This document provides instructions for AI assistants working on the Bunnyshell CLI codebase. It is maintained in an AI-agnostic format for use with any AI development assistant. |
| 4 | + |
| 5 | +## Quick Start |
| 6 | + |
| 7 | +**Project:** Bunnyshell CLI (`bns`) |
| 8 | +**Language:** Go 1.23 |
| 9 | +**Development Environment:** Docker-based (required) |
| 10 | + |
| 11 | +## Development Environment Setup |
| 12 | + |
| 13 | +### Using Docker (Required for Go Commands) |
| 14 | + |
| 15 | +All Go-related commands MUST be executed inside the Docker development container. |
| 16 | + |
| 17 | +**1. Check if container is running:** |
| 18 | +```bash |
| 19 | +docker ps --filter "name=bunnyshell-cli" |
| 20 | +``` |
| 21 | + |
| 22 | +**2. Start container if needed:** |
| 23 | +```bash |
| 24 | +cd .dev && docker-compose up -d |
| 25 | +``` |
| 26 | + |
| 27 | +**3. Execute commands in container:** |
| 28 | +```bash |
| 29 | +docker exec -it bunnyshell-cli <command> |
| 30 | +``` |
| 31 | + |
| 32 | +**Common commands:** |
| 33 | +```bash |
| 34 | +# Build the project |
| 35 | +docker exec -it bunnyshell-cli make build-local |
| 36 | + |
| 37 | +# Run go mod tidy |
| 38 | +docker exec -it bunnyshell-cli go mod tidy |
| 39 | + |
| 40 | +# Run tests |
| 41 | +docker exec -it bunnyshell-cli go test ./... |
| 42 | + |
| 43 | +# Access container shell |
| 44 | +docker exec -it bunnyshell-cli /bin/bash |
| 45 | +``` |
| 46 | + |
| 47 | +### Build Success Criteria |
| 48 | + |
| 49 | +A build is considered **successful** when: |
| 50 | +- ✅ Linux binary is produced: `dist/bns_linux_amd64_v1/bns` |
| 51 | +- ✅ Darwin binary is produced: `dist/bns_darwin_arm64/bns` or `dist/bns_darwin_amd64_v1/bns` |
| 52 | + |
| 53 | +A build may show errors for: |
| 54 | +- ❌ Docker image building (no Docker-in-Docker in dev container) - **THIS IS EXPECTED AND OK** |
| 55 | + |
| 56 | +### Testing Builds |
| 57 | + |
| 58 | +**From container:** |
| 59 | +```bash |
| 60 | +./dist/bns_linux_amd64_v1/bns --help |
| 61 | +``` |
| 62 | + |
| 63 | +**From host (macOS):** |
| 64 | +```bash |
| 65 | +./dist/bns_darwin_arm64/bns --help |
| 66 | +# or |
| 67 | +./dist/bns_darwin_amd64_v1/bns --help |
| 68 | +``` |
| 69 | + |
| 70 | +### Important Notes |
| 71 | + |
| 72 | +- **DO NOT** rely on host machine Go installation |
| 73 | +- **DO NOT** run `go` commands directly on the host |
| 74 | +- **ALWAYS** use the Docker container for Go commands |
| 75 | +- The host may not have Go installed or may have a different version |
| 76 | + |
| 77 | +## Project Structure |
| 78 | + |
| 79 | +``` |
| 80 | +/ |
| 81 | +├── .dev/ # Docker development environment |
| 82 | +│ ├── docker-compose.yaml # Container setup |
| 83 | +│ ├── Dockerfile.dev # golang:1.23 with goreleaser |
| 84 | +│ └── Readme.md # Quick reference |
| 85 | +├── cmd/ # Command implementations |
| 86 | +│ └── [resource]/ # Command groups (environments, components, etc.) |
| 87 | +│ ├── root.go # Main command |
| 88 | +│ ├── list.go # List subcommand |
| 89 | +│ ├── show.go # Show subcommand |
| 90 | +│ └── action/ # Action subcommands (create, delete, etc.) |
| 91 | +├── pkg/ # Core packages |
| 92 | +│ ├── api/ # API client wrappers |
| 93 | +│ ├── config/ # Configuration management |
| 94 | +│ ├── formatter/ # Output formatters (stylish, JSON, YAML) |
| 95 | +│ ├── interactive/ # Interactive prompts |
| 96 | +│ └── ... # Other core packages |
| 97 | +├── main.go # Application entry point |
| 98 | +├── go.mod / go.sum # Go dependencies |
| 99 | +├── Makefile # Build targets |
| 100 | +├── .goreleaser.yaml # Release configuration |
| 101 | +└── AGENTS.md # This file |
| 102 | +``` |
| 103 | + |
| 104 | +## Adding a New Command |
| 105 | + |
| 106 | +Follow this pattern when adding new commands: |
| 107 | + |
| 108 | +### 1. Create API Layer |
| 109 | + |
| 110 | +Location: `pkg/api/[resource]/` |
| 111 | + |
| 112 | +```go |
| 113 | +// pkg/api/[resource]/list.go |
| 114 | +package resource |
| 115 | + |
| 116 | +import ( |
| 117 | + "bunnyshell.com/cli/pkg/api" |
| 118 | + "bunnyshell.com/cli/pkg/api/common" |
| 119 | + "bunnyshell.com/cli/pkg/lib" |
| 120 | + "bunnyshell.com/sdk" |
| 121 | +) |
| 122 | + |
| 123 | +type ListOptions struct { |
| 124 | + common.ListOptions |
| 125 | + // Add your filters here |
| 126 | +} |
| 127 | + |
| 128 | +func NewListOptions() *ListOptions { |
| 129 | + return &ListOptions{ |
| 130 | + ListOptions: *common.NewListOptions(), |
| 131 | + } |
| 132 | +} |
| 133 | + |
| 134 | +func List(options *ListOptions) (*sdk.PaginatedResourceCollection, error) { |
| 135 | + model, resp, err := ListRaw(options) |
| 136 | + if err != nil { |
| 137 | + return nil, api.ParseError(resp, err) |
| 138 | + } |
| 139 | + return model, nil |
| 140 | +} |
| 141 | + |
| 142 | +func ListRaw(options *ListOptions) (*sdk.PaginatedResourceCollection, *http.Response, error) { |
| 143 | + profile := options.GetProfile() |
| 144 | + ctx, cancel := lib.GetContextFromProfile(profile) |
| 145 | + defer cancel() |
| 146 | + |
| 147 | + request := lib.GetAPIFromProfile(profile).ResourceAPI.ResourceList(ctx) |
| 148 | + return applyOptions(request, options).Execute() |
| 149 | +} |
| 150 | + |
| 151 | +func applyOptions(request sdk.ApiResourceListRequest, options *ListOptions) sdk.ApiResourceListRequest { |
| 152 | + if options == nil { |
| 153 | + return request |
| 154 | + } |
| 155 | + |
| 156 | + if options.Page > 1 { |
| 157 | + request = request.Page(options.Page) |
| 158 | + } |
| 159 | + |
| 160 | + // Add your filters here |
| 161 | + |
| 162 | + return request |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +### 2. Create Command Implementation |
| 167 | + |
| 168 | +Location: `cmd/[resource]/` |
| 169 | + |
| 170 | +```go |
| 171 | +// cmd/[resource]/list.go |
| 172 | +package resource |
| 173 | + |
| 174 | +import ( |
| 175 | + "bunnyshell.com/cli/pkg/api/resource" |
| 176 | + "bunnyshell.com/cli/pkg/lib" |
| 177 | + "github.com/spf13/cobra" |
| 178 | +) |
| 179 | + |
| 180 | +func init() { |
| 181 | + listOptions := resource.NewListOptions() |
| 182 | + |
| 183 | + command := &cobra.Command{ |
| 184 | + Use: "list", |
| 185 | + Short: "List resources", |
| 186 | + ValidArgsFunction: cobra.NoFileCompletions, |
| 187 | + |
| 188 | + RunE: func(cmd *cobra.Command, args []string) error { |
| 189 | + return lib.ShowCollection(cmd, listOptions, func() (lib.ModelWithPagination, error) { |
| 190 | + return resource.List(listOptions) |
| 191 | + }) |
| 192 | + }, |
| 193 | + } |
| 194 | + |
| 195 | + flags := command.Flags() |
| 196 | + // Add your flags here |
| 197 | + listOptions.UpdateFlagSet(flags) |
| 198 | + |
| 199 | + mainCmd.AddCommand(command) |
| 200 | +} |
| 201 | +``` |
| 202 | + |
| 203 | +### 3. Add Formatter (if needed) |
| 204 | + |
| 205 | +Location: `pkg/formatter/` |
| 206 | + |
| 207 | +```go |
| 208 | +// pkg/formatter/stylish.resource.go |
| 209 | +package formatter |
| 210 | + |
| 211 | +import ( |
| 212 | + "fmt" |
| 213 | + "text/tabwriter" |
| 214 | + "bunnyshell.com/sdk" |
| 215 | +) |
| 216 | + |
| 217 | +func tabulateResourceCollection(writer *tabwriter.Writer, data *sdk.PaginatedResourceCollection) { |
| 218 | + fmt.Fprintf(writer, "%v\t %v\t %v\n", "ID", "Name", "Status") |
| 219 | + |
| 220 | + if data.Embedded != nil { |
| 221 | + for _, item := range data.Embedded.Item { |
| 222 | + fmt.Fprintf(writer, "%v\t %v\t %v\n", |
| 223 | + item.GetId(), |
| 224 | + item.GetName(), |
| 225 | + item.GetStatus(), |
| 226 | + ) |
| 227 | + } |
| 228 | + } |
| 229 | +} |
| 230 | +``` |
| 231 | + |
| 232 | +Then add the case to `pkg/formatter/stylish.go`: |
| 233 | + |
| 234 | +```go |
| 235 | +case *sdk.PaginatedResourceCollection: |
| 236 | + tabulateResourceCollection(writer, dataType) |
| 237 | +``` |
| 238 | + |
| 239 | +## Common Patterns |
| 240 | + |
| 241 | +### Flag Conflicts to Avoid |
| 242 | + |
| 243 | +These global flags are already registered: |
| 244 | +- `-t` = `--timeout` |
| 245 | +- `-d` = `--debug` |
| 246 | +- `-v` = `--verbose` |
| 247 | +- `-o` = `--output` |
| 248 | + |
| 249 | +Do not use these shorthands for command-specific flags. |
| 250 | + |
| 251 | +### Repeatable Flags |
| 252 | + |
| 253 | +Use `StringArrayVar` for repeatable flags: |
| 254 | + |
| 255 | +```go |
| 256 | +var statuses []string |
| 257 | +flags.StringArrayVar(&statuses, "status", statuses, "Filter by status (repeatable)") |
| 258 | +``` |
| 259 | + |
| 260 | +### Required Flags |
| 261 | + |
| 262 | +```go |
| 263 | +flags.AddFlag(option.GetRequiredFlag("id")) |
| 264 | +``` |
| 265 | + |
| 266 | +### Optional Context-aware Flags |
| 267 | + |
| 268 | +```go |
| 269 | +flags.AddFlag(options.Organization.GetFlag("organization")) |
| 270 | +``` |
| 271 | + |
| 272 | +## Testing Your Changes |
| 273 | + |
| 274 | +### 1. Build the project |
| 275 | + |
| 276 | +```bash |
| 277 | +docker exec -it bunnyshell-cli make build-local |
| 278 | +``` |
| 279 | + |
| 280 | +### 2. Verify build succeeded |
| 281 | + |
| 282 | +Check for: |
| 283 | +- `dist/bns_linux_amd64_v1/bns` (Linux) |
| 284 | +- `dist/bns_darwin_arm64/bns` (macOS ARM) |
| 285 | +- `dist/bns_darwin_amd64_v1/bns` (macOS Intel) |
| 286 | + |
| 287 | +### 3. Test the binary |
| 288 | + |
| 289 | +From host (macOS): |
| 290 | +```bash |
| 291 | +./dist/bns_darwin_arm64/bns [your-command] --help |
| 292 | +``` |
| 293 | + |
| 294 | +From container (Linux): |
| 295 | +```bash |
| 296 | +./dist/bns_linux_amd64_v1/bns [your-command] --help |
| 297 | +``` |
| 298 | + |
| 299 | +## SDK Dependencies |
| 300 | + |
| 301 | +The project depends on: |
| 302 | +- `bunnyshell.com/sdk` - Official Bunnyshell API client |
| 303 | +- `bunnyshell.com/dev` - Development utilities |
| 304 | + |
| 305 | +If you need to test with local SDK changes: |
| 306 | + |
| 307 | +1. Add to `go.mod`: |
| 308 | + ```go |
| 309 | + replace bunnyshell.com/dev v0.7.0 => ../bunnyshellosi-dev/ |
| 310 | + ``` |
| 311 | + |
| 312 | +2. Ensure the path exists in both container and host (for IDE support) |
| 313 | + |
| 314 | +3. The docker-compose.yaml already mounts this path |
| 315 | + |
| 316 | +## Code Quality |
| 317 | + |
| 318 | +### Before committing: |
| 319 | + |
| 320 | +```bash |
| 321 | +# Format code |
| 322 | +docker exec -it bunnyshell-cli go fmt ./... |
| 323 | + |
| 324 | +# Tidy dependencies |
| 325 | +docker exec -it bunnyshell-ci go mod tidy |
| 326 | + |
| 327 | +# Run tests |
| 328 | +docker exec -it bunnyshell-cli go test ./... |
| 329 | + |
| 330 | +# Build to verify |
| 331 | +docker exec -it bunnyshell-cli make build-local |
| 332 | +``` |
| 333 | + |
| 334 | +## Architecture Principles |
| 335 | + |
| 336 | +1. **Separation of Concerns:** |
| 337 | + - `cmd/` = CLI interface and command handling |
| 338 | + - `pkg/api/` = Business logic and API interaction |
| 339 | + - `pkg/formatter/` = Output formatting |
| 340 | + - `pkg/lib/` = Shared utilities |
| 341 | + |
| 342 | +2. **Configuration Management:** |
| 343 | + - Support multiple profiles |
| 344 | + - Store context (org, project, env, component) |
| 345 | + - Allow flag overrides |
| 346 | + - Interactive prompts for missing values |
| 347 | + |
| 348 | +3. **User Experience:** |
| 349 | + - Provide interactive mode for missing parameters |
| 350 | + - Support non-interactive mode for automation |
| 351 | + - Multiple output formats (stylish, JSON, YAML) |
| 352 | + - Progress indicators for long operations |
| 353 | + |
| 354 | +4. **Error Handling:** |
| 355 | + - Use domain-specific error types |
| 356 | + - Parse and format API errors |
| 357 | + - Provide helpful error messages |
| 358 | + |
| 359 | +## Documentation |
| 360 | + |
| 361 | +When adding features, update: |
| 362 | +- **AGENTS.md** (this file) - For AI-agnostic instructions |
| 363 | +- **CLAUDE.md** - For detailed codebase overview |
| 364 | +- **README.md** - For user-facing documentation |
| 365 | +- `.dev/Readme.md` - For development quick reference |
| 366 | + |
| 367 | +## Getting Help |
| 368 | + |
| 369 | +- Check **CLAUDE.md** for comprehensive codebase documentation |
| 370 | +- Look at existing commands for patterns (e.g., `cmd/pipeline/jobs.go`) |
| 371 | +- Examine API packages for SDK usage (e.g., `pkg/api/workflow_job/list.go`) |
| 372 | +- Review formatters for output patterns (e.g., `pkg/formatter/stylish.workflow_job.go`) |
0 commit comments