Skip to content

Commit 93b33aa

Browse files
authored
Add AGENTs.md files to the cli (#271)
* Add AGENTs.md files to cli repo * Add data access pattern
1 parent 981a38f commit 93b33aa

2 files changed

Lines changed: 291 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Repository Guidelines
2+
3+
> **Last verified**: 2026-02-10
4+
5+
Essential guidance for AI coding agents working in the Render CLI.
6+
7+
## Project Overview
8+
9+
**What**: Terminal interface for Render platform (Go + Bubble Tea TUI)
10+
**Why**: Manage deployments, services, logs, and infrastructure from the command line
11+
12+
## Before You Code
13+
14+
1. Search existing code for similar implementations
15+
2. Follow established patterns in `pkg/tui/`
16+
3. Check if types exist in `pkg/client/` before defining new ones
17+
18+
## Boundaries
19+
20+
**Never do (without explicit approval)**:
21+
- Edit files in `pkg/client/` (generated code)
22+
- Modify `*_gen.go` files
23+
- Remove existing tests
24+
- Change navigation stack architecture in `pkg/tui/stack.go`
25+
26+
**Always ask first**:
27+
- Major TUI architectural changes
28+
- Adding new dependencies
29+
- Changes to command flag APIs (breaking changes)
30+
31+
**Safe to do**:
32+
- Add new views in `pkg/tui/views/`
33+
- Add new commands in `cmd/`
34+
- Write tests anywhere
35+
- Refactor within a single package
36+
37+
## When Uncertain
38+
39+
- **Requirements unclear**: Ask before implementing
40+
- **Architecture questions**: Propose minimal approach, get feedback
41+
- **TUI interactions**: Describe intended flow, get confirmation
42+
43+
Prefer small, incremental changes over large speculative implementations.
44+
45+
## Essential Commands
46+
47+
```bash
48+
# Building
49+
go build -o render . # Build binary
50+
51+
# Testing
52+
go test ./... # All tests
53+
go test -run TestName ./pkg/... # Single test
54+
55+
# Linting & Formatting
56+
golangci-lint run # Lint
57+
pre-commit run --all-files # All hooks
58+
59+
# Type Generation (from public-api-schema)
60+
export RENDER_API_PATH=/path/to/api
61+
cd ../public-api-schema && ./generate-cli.sh
62+
```
63+
64+
## Project Structure
65+
66+
- `cmd/` - Cobra command definitions
67+
- `pkg/client/` - **Generated** API client (READ-ONLY)
68+
- `pkg/tui/` - Bubble Tea TUI framework ([see AGENTS.md](pkg/tui/AGENTS.md))
69+
- `pkg/config/` - User config file (`~/.render/cli.yaml`)
70+
- `pkg/cfg/` - Environment defaults (different from config!)
71+
- `pkg/command/` - Output formats, context utilities
72+
- `pkg/dependencies/` - Dependency injection container
73+
- `pkg/style/` - Lipgloss styling system
74+
75+
## Key Patterns
76+
77+
**TUI**: Elm Architecture (Message → Update → View). See [pkg/tui/AGENTS.md](pkg/tui/AGENTS.md).
78+
79+
**Data Access**: Service → Repo → Client (three-layer architecture):
80+
- **Client** (`pkg/client/`): Generated HTTP client - never edit
81+
- **Repo** (`pkg/*/repo.go`): Wraps client, handles pagination & error parsing
82+
- **Service** (`pkg/*/service.go`): Business logic, orchestrates multiple repos, enriches data
83+
84+
```go
85+
// Service combines data from multiple repos
86+
svc, _ := s.repo.ListServices(ctx, params) // calls client internally
87+
proj, _ := s.projectRepo.ListProjects(ctx) // different repo
88+
return s.enrich(svc, proj) // business logic
89+
```
90+
91+
**Output formats**: Use `command.IsInteractive(ctx)` for branching:
92+
```go
93+
if command.IsInteractive(ctx) {
94+
return runTUI(deps)
95+
}
96+
return runNonInteractive(ctx, deps) // JSON, YAML, TEXT
97+
```
98+
99+
**Naming**:
100+
| Element | Pattern | Example |
101+
|---------|---------|---------|
102+
| Commands | `New{Action}Cmd()` | `NewServiceListCmd()` |
103+
| Models | `{Entity}Model` | `ServiceListModel` |
104+
| Messages | `{Action}Msg` | `LoadServicesMsg` |
105+
106+
## Testing Against Local Dev API
107+
108+
```bash
109+
# Start API
110+
tilt up api
111+
curl -k https://api.localhost.render.com:8443/health
112+
113+
# Configure CLI
114+
export RENDER_HOST="https://api.localhost.render.com:8443/v1/"
115+
export RENDER_API_KEY="your-api-key"
116+
./render services list
117+
```
118+
119+
| Variable | Description |
120+
|----------|-------------|
121+
| `RENDER_HOST` | API base URL |
122+
| `RENDER_API_KEY` | API key (skips OAuth) |
123+
| `RENDER_WORKSPACE` | Workspace ID override |
124+
| `RENDER_CLI_CONFIG_PATH` | Config file path override |
125+
126+
## Testing
127+
128+
- **Table-driven tests** with `stretchr/testify`
129+
- **Manual fakes** in `pkg/tui/testhelper/`
130+
- **Pre-commit hooks**: golangci-lint, go fmt, yaml checks, large file detection
131+
132+
## Common Gotchas
133+
134+
- Don't block in `Update()` - use `tea.Cmd` for async work
135+
- Handle `tea.KeyCtrlC` and `tea.KeyCtrlD` for proper exit
136+
- Run `reset` if terminal breaks after a crash
137+
- `Push()` returns a `tea.Cmd` that must be returned from `Update()`
138+
- Generated types are read-only - regenerate via `generate-cli.sh`
139+
140+
## Package-Specific Guides
141+
142+
- `pkg/tui/`: [See AGENTS.md](pkg/tui/AGENTS.md) for Bubble Tea patterns and examples
143+
144+
## Reference
145+
146+
- [Bubble Tea](https://github.com/charmbracelet/bubbletea) | [Cobra](https://github.com/spf13/cobra) | [Render CLI Docs](https://render.com/docs/cli)

pkg/tui/AGENTS.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# TUI Development Guide
2+
3+
> **Last verified**: 2026-02-10
4+
5+
Detailed patterns for Bubble Tea TUI development in the Render CLI.
6+
7+
For boundaries (Never/Ask/Safe) and escalation guidance, see the [main AGENTS.md](../../AGENTS.md#boundaries).
8+
9+
## Architecture
10+
11+
The CLI uses the [Elm Architecture](https://guide.elm-lang.org/architecture/):
12+
`Message → Update(model) → View(model) → Render`
13+
14+
Every component implements: `Init()`, `Update(msg tea.Msg)`, `View()`
15+
16+
---
17+
18+
## Stack-based Navigation
19+
20+
The `StackModel` in `stack.go` manages view navigation with breadcrumbs.
21+
22+
```go
23+
// Push returns a tea.Cmd - must be returned from Update()
24+
cmd := stack.Push(ModelWithCmd{
25+
Model: myModel,
26+
Cmd: "render services list", // For clipboard
27+
Breadcrumb: "Services",
28+
})
29+
return m, cmd
30+
```
31+
32+
---
33+
34+
## Message Patterns
35+
36+
```go
37+
// Name messages with Action + Msg suffix
38+
type LoadingDataMsg struct{}
39+
type DataLoadedMsg struct{ Data []Item }
40+
type ErrorMsg struct{ Err error }
41+
```
42+
43+
---
44+
45+
## Async Commands
46+
47+
Never block in `Update()`. Use commands for I/O:
48+
49+
```go
50+
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
51+
switch msg := msg.(type) {
52+
case tea.KeyMsg:
53+
return m, m.fetchData() // Return command, don't block
54+
case DataLoadedMsg:
55+
m.data = msg.Data
56+
return m, nil
57+
}
58+
return m, nil
59+
}
60+
61+
func (m Model) fetchData() tea.Cmd {
62+
return func() tea.Msg {
63+
data, err := m.repo.List()
64+
if err != nil {
65+
return ErrorMsg{Err: err}
66+
}
67+
return DataLoadedMsg{Data: data}
68+
}
69+
}
70+
```
71+
72+
---
73+
74+
## Subcomponent Updates
75+
76+
Always delegate updates to child components:
77+
78+
```go
79+
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
80+
var cmds []tea.Cmd
81+
var cmd tea.Cmd
82+
83+
m.table, cmd = m.table.Update(msg)
84+
cmds = append(cmds, cmd)
85+
86+
m.input, cmd = m.input.Update(msg)
87+
cmds = append(cmds, cmd)
88+
89+
return m, tea.Batch(cmds...)
90+
}
91+
```
92+
93+
---
94+
95+
## Debugging & Testing
96+
97+
```go
98+
// Log to file (stdout is occupied by TUI)
99+
f, _ := tea.LogToFile("debug.log", "debug")
100+
defer f.Close()
101+
```
102+
103+
```bash
104+
tail -f debug.log # Watch logs in another terminal
105+
go test ./pkg/tui/... # Run TUI tests
106+
```
107+
108+
If terminal breaks after crash, run `reset`.
109+
110+
**Test patterns**: Table-driven tests with `stretchr/testify`, manual fakes in `testhelper/`
111+
112+
```go
113+
func TestMyView(t *testing.T) {
114+
fake := &testhelper.FakeDimensionModel{Value: "test"}
115+
model := NewMyModel(fake)
116+
// Assert on View() output or model state
117+
}
118+
```
119+
120+
---
121+
122+
## Styling
123+
124+
Use `pkg/style/` for consistent styling. Never hardcode dimensions—use `lipgloss.Height()` and `lipgloss.Width()`.
125+
126+
```go
127+
title := style.Title.Render("My Title")
128+
height := lipgloss.Height(rendered)
129+
```
130+
131+
---
132+
133+
## Common Mistakes
134+
135+
- Blocking in `Update()` - use commands for async work
136+
- Forgetting to handle `tea.KeyCtrlC` and `tea.KeyCtrlD`
137+
- Not returning the `tea.Cmd` from `Push()`
138+
- Hardcoding dimensions instead of using lipgloss
139+
- Message ordering from concurrent commands is undefined
140+
141+
---
142+
143+
## Reference
144+
145+
[Bubble Tea](https://github.com/charmbracelet/bubbletea) | [Lipgloss](https://github.com/charmbracelet/lipgloss) | [Bubbles](https://github.com/charmbracelet/bubbles)

0 commit comments

Comments
 (0)