Skip to content

fix: refactor agent type parsing and run lint in CI #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions .github/workflows/go-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,34 @@ name: Go Tests

on:
push:
branches: [ main ]
branches: [main]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 'stable'
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "stable"

- name: Test
run: go test -count=1 -v ./...
- name: Test
run: go test -count=1 -v ./...

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "stable"

- name: golangci-lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
with:
version: v2.1
12 changes: 12 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: "2"
linters:
enable:
- exhaustive
settings:
exhaustive:
check:
- "switch"
- "map"
staticcheck:
checks:
- "-QF1001" # disable "could apply De Morgan's law"
12 changes: 9 additions & 3 deletions cmd/attach/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ func ReadScreenOverHTTP(ctx context.Context, url string, ch chan<- httpapi.Scree
if err != nil {
return xerrors.Errorf("failed to do request: %w", err)
}
defer res.Body.Close()
defer func() {
_ = res.Body.Close()
}()

for ev, err := range sse.Read(res.Body, &sse.ReadConfig{
// 256KB: screen can be big. The default terminal size is 80x1000,
Expand Down Expand Up @@ -115,7 +117,9 @@ func WriteRawInputOverHTTP(ctx context.Context, url string, msg string) error {
if err != nil {
return xerrors.Errorf("failed to do request: %w", err)
}
defer res.Body.Close()
defer func() {
_ = res.Body.Close()
}()
if res.StatusCode != http.StatusOK {
return xerrors.Errorf("failed to write raw input: %w", errors.New(res.Status))
}
Expand All @@ -132,7 +136,9 @@ func runAttach(remoteUrl string) error {
if err != nil {
return xerrors.Errorf("failed to make raw: %w", err)
}
defer term.Restore(stdin, oldState)
defer func() {
_ = term.Restore(stdin, oldState)
}()

stdinWriter := &ChannelWriter{
ch: make(chan []byte, 4096),
Expand Down
70 changes: 33 additions & 37 deletions cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"log/slog"
"net/http"
"os"
"sort"
"strings"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -38,45 +39,31 @@ const (
AgentTypeCustom AgentType = msgfmt.AgentTypeCustom
)

// exhaustiveness of this map is checked by the exhaustive linter
var agentTypeMap = map[AgentType]bool{
AgentTypeClaude: true,
AgentTypeGoose: true,
AgentTypeAider: true,
AgentTypeCodex: true,
AgentTypeGemini: true,
AgentTypeCustom: true,
}

func parseAgentType(firstArg string, agentTypeVar string) (AgentType, error) {
var agentType AgentType
switch agentTypeVar {
case string(AgentTypeClaude):
agentType = AgentTypeClaude
case string(AgentTypeGoose):
agentType = AgentTypeGoose
case string(AgentTypeAider):
agentType = AgentTypeAider
case string(AgentTypeGemini):
agentType = AgentTypeGemini
case string(AgentTypeCustom):
agentType = AgentTypeCustom
case string(AgentTypeCodex):
agentType = AgentTypeCodex
case "":
// do nothing
default:
return "", fmt.Errorf("invalid agent type: %s", agentTypeVar)
// if the agent type is provided, use it
castedAgentType := AgentType(agentTypeVar)
if _, ok := agentTypeMap[castedAgentType]; ok {
return castedAgentType, nil
}
if agentType != "" {
return agentType, nil
if agentTypeVar != "" {
return AgentTypeCustom, fmt.Errorf("invalid agent type: %s", agentTypeVar)
}

switch firstArg {
case string(AgentTypeClaude):
agentType = AgentTypeClaude
case string(AgentTypeGoose):
agentType = AgentTypeGoose
case string(AgentTypeAider):
agentType = AgentTypeAider
case string(AgentTypeCodex):
agentType = AgentTypeCodex
case string(AgentTypeGemini):
agentType = AgentTypeGemini
default:
agentType = AgentTypeCustom
// if the agent type is not provided, guess it from the first argument
castedFirstArg := AgentType(firstArg)
if _, ok := agentTypeMap[castedFirstArg]; ok {
return castedFirstArg, nil
}
return agentType, nil
return AgentTypeCustom, nil
}

func runServer(ctx context.Context, logger *slog.Logger, argsToPass []string) error {
Expand Down Expand Up @@ -139,10 +126,19 @@ func runServer(ctx context.Context, logger *slog.Logger, argsToPass []string) er
return nil
}

var agentNames = (func() []string {
names := make([]string, 0, len(agentTypeMap))
for agentType := range agentTypeMap {
names = append(names, string(agentType))
}
sort.Strings(names)
return names
})()

var ServerCmd = &cobra.Command{
Use: "server [agent]",
Short: "Run the server",
Long: `Run the server with the specified agent (claude, goose, aider, gemini, codex)`,
Long: fmt.Sprintf("Run the server with the specified agent (one of: %s)", strings.Join(agentNames, ", ")),
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
Expand All @@ -155,7 +151,7 @@ var ServerCmd = &cobra.Command{
}

func init() {
ServerCmd.Flags().StringVarP(&agentTypeVar, "type", "t", "", "Override the agent type (one of: claude, goose, aider, custom)")
ServerCmd.Flags().StringVarP(&agentTypeVar, "type", "t", "", fmt.Sprintf("Override the agent type (one of: %s, custom)", strings.Join(agentNames, ", ")))
ServerCmd.Flags().IntVarP(&port, "port", "p", 3284, "Port to run the server on")
ServerCmd.Flags().BoolVarP(&printOpenAPI, "print-openapi", "P", false, "Print the OpenAPI schema to stdout and exit")
ServerCmd.Flags().StringVarP(&chatBasePath, "chat-base-path", "c", "/chat", "Base path for assets and routes used in the static files of the chat interface")
Expand Down
4 changes: 3 additions & 1 deletion lib/httpapi/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ func FileServerWithIndexFallback(chatBasePath string) http.Handler {
// Try to serve the file directly
f, err := chatFS.Open(trimmedPath)
if err == nil {
defer f.Close()
defer func() {
_ = f.Close()
}()
fileServer.ServeHTTP(w, r)
return
}
Expand Down
4 changes: 3 additions & 1 deletion lib/httpapi/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ func TestOpenAPISchema(t *testing.T) {
if err != nil {
t.Fatalf("failed to open disk schema: %s", err)
}
defer diskSchemaFile.Close()
defer func() {
_ = diskSchemaFile.Close()
}()

diskSchemaBytes, err := io.ReadAll(diskSchemaFile)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion lib/screentracker/conversation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func TestMessages(t *testing.T) {
assert.Equal(t, []st.ConversationMessage{
agentMsg(0, "1"),
}, msgs)
nowWrapper.Time = nowWrapper.Time.Add(1 * time.Second)
nowWrapper.Time = nowWrapper.Add(1 * time.Second)
c.AddSnapshot("1")
assert.Equal(t, msgs, c.Messages())
})
Expand Down