Skip to content
Closed
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*.dll
*.so
*.dylib
agentmail

# Test binary, built with `go test -c`
*.test
Expand Down
87 changes: 87 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

AgentMail is a Golang CLI application built using the [Cobra](https://github.com/spf13/cobra) framework. The project follows the standard Cobra architectural pattern with a modular command structure.

## Development Commands

### Building
```bash
go build -o agentmail
```

### Testing
```bash
# Run all tests
go test ./cmd/... -v

# Run tests with coverage
go test ./cmd/... -cover

# Run a specific test
go test ./cmd -v -run TestHelloCommand

# Run a specific sub-test
go test ./cmd -v -run TestHelloCommand/custom_name_with_--name
```

### Running the CLI
```bash
./agentmail --help
./agentmail version
./agentmail hello --name Alice
```

## Architecture

### Command Structure

The application uses Cobra's command registration pattern:

1. **`main.go`** - Entry point that calls `cmd.Execute()`
2. **`cmd/root.go`** - Defines the root command and `Execute()` function
3. **`cmd/<command>.go`** - Individual command files that auto-register via `init()`

### Adding a New Command

To add a new command:

1. Create `cmd/<commandname>.go`
2. Define the command using `&cobra.Command{}`
3. Register it in `init()` with `rootCmd.AddCommand(<commandname>Cmd)`
4. Create corresponding `cmd/<commandname>_test.go` for tests

Example pattern:
```go
var myCmd = &cobra.Command{
Use: "mycommand",
Short: "Brief description",
Long: "Detailed description",
Run: func(cmd *cobra.Command, args []string) {
// Command logic
},
}

func init() {
rootCmd.AddCommand(myCmd)
// Add flags with: myCmd.Flags().StringVarP(...)
}
```

### Command Registration

All commands in `cmd/` package are automatically registered through `init()` functions. The `init()` functions execute before `main()`, registering commands with `rootCmd` via `rootCmd.AddCommand()`.

### Testing Pattern

Tests create isolated Cobra command instances to avoid state pollution between tests. Each test constructs its own command tree and uses buffers to capture output for assertions.

## Code Conventions

- **Version**: Defined as a constant in `cmd/version.go`
- **Flags**: Use `StringVarP()` for flags with both long (`--name`) and short (`-n`) forms
- **Package-level vars**: Used for flag values (e.g., `var helloName string`)
- **Module path**: `github.com/UserAd/AgentMail`
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,47 @@
# AgentMail
# AgentMail

A Golang CLI application built with [Cobra](https://github.com/spf13/cobra).

## Installation

```bash
go build -o agentmail
```

## Usage

```bash
# Show help
./agentmail help

# Show version
./agentmail version

# Say hello
./agentmail hello
./agentmail hello --name Alice
./agentmail hello -n Bob # short flag
```

## Commands

- `version` - Display version information
- `hello` - Say hello with optional name flag (--name or -n)
- `help` - Show usage information
- `completion` - Generate shell autocompletion scripts

## Development

### Running Tests

```bash
# Run all tests
go test ./cmd/... -v

# Run with coverage
go test ./cmd/... -cover
```

## Technology Stack

- [Cobra](https://github.com/spf13/cobra) - Modern CLI framework for Go
23 changes: 23 additions & 0 deletions cmd/hello.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package cmd

import (
"fmt"

"github.com/spf13/cobra"
)

var helloName string

var helloCmd = &cobra.Command{
Use: "hello",
Short: "Say hello to someone",
Long: `Say hello to a person. You can specify a custom name using the --name flag.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Hello, %s!\n", helloName)
},
}

func init() {
rootCmd.AddCommand(helloCmd)
helloCmd.Flags().StringVarP(&helloName, "name", "n", "World", "Name to greet")
}
68 changes: 68 additions & 0 deletions cmd/hello_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package cmd

import (
"bytes"
"strings"
"testing"

"github.com/spf13/cobra"
)

func TestHelloCommand(t *testing.T) {
tests := []struct {
name string
args []string
want string
}{
{
name: "default greeting",
args: []string{"hello"},
want: "Hello, World!",
},
{
name: "custom name with --name",
args: []string{"hello", "--name", "Alice"},
want: "Hello, Alice!",
},
{
name: "custom name with -n",
args: []string{"hello", "-n", "Bob"},
want: "Hello, Bob!",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a buffer to capture output
buf := new(bytes.Buffer)

// Create a new root command for testing
testRootCmd := &cobra.Command{Use: "agentmail"}
var testName string
testHelloCmd := &cobra.Command{
Use: "hello",
Short: "Say hello",
Run: func(cmd *cobra.Command, args []string) {
buf.WriteString("Hello, " + testName + "!\n")
},
}
testHelloCmd.Flags().StringVarP(&testName, "name", "n", "World", "Name to greet")
testRootCmd.AddCommand(testHelloCmd)
testRootCmd.SetOut(buf)
testRootCmd.SetArgs(tt.args)

// Execute the command
err := testRootCmd.Execute()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

// Check the output
got := strings.TrimSpace(buf.String())
want := tt.want
if got != want {
t.Errorf("got %q, want %q", got, want)
}
})
}
}
25 changes: 25 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package cmd

import (
"fmt"
"os"

"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
Use: "agentmail",
Short: "AgentMail - A basic Golang CLI application",
Long: `AgentMail is a CLI application built with Cobra for managing email operations.`,
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

func init() {
// Add global flags here if needed
}
29 changes: 29 additions & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package cmd

import (
"bytes"
"testing"
)

func TestRootCommand(t *testing.T) {
// Create a buffer to capture output
buf := new(bytes.Buffer)

// Reset rootCmd for testing
testCmd := rootCmd
testCmd.SetOut(buf)
testCmd.SetErr(buf)
testCmd.SetArgs([]string{"--help"})

// Execute the command
err := testCmd.Execute()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

// Check the output contains expected text
got := buf.String()
if got == "" {
t.Error("Expected help output, got empty string")
}
}
27 changes: 27 additions & 0 deletions cmd/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package cmd

import (
"fmt"

"github.com/spf13/cobra"
)

const version = "0.1.0"

var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of AgentMail",
Long: `Display the current version of AgentMail CLI application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("AgentMail version %s\n", version)
},
}

func init() {
rootCmd.AddCommand(versionCmd)
}

// GetVersion returns the current version
func GetVersion() string {
return version
}
50 changes: 50 additions & 0 deletions cmd/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package cmd

import (
"bytes"
"strings"
"testing"

"github.com/spf13/cobra"
)

func TestVersionCommand(t *testing.T) {
// Create a buffer to capture output
buf := new(bytes.Buffer)

// Create a new root command for testing
testRootCmd := &cobra.Command{Use: "agentmail"}
testVersionCmd := &cobra.Command{
Use: "version",
Short: "Print the version number",
Run: func(cmd *cobra.Command, args []string) {
buf.WriteString("AgentMail version " + GetVersion() + "\n")
},
}
testRootCmd.AddCommand(testVersionCmd)
testRootCmd.SetOut(buf)
testRootCmd.SetArgs([]string{"version"})

// Execute the command
err := testRootCmd.Execute()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

// Check the output
got := buf.String()
want := "AgentMail version " + version
if !strings.Contains(got, want) {
t.Errorf("got %q, want %q", got, want)
}
}

func TestGetVersion(t *testing.T) {
v := GetVersion()
if v == "" {
t.Error("GetVersion() returned empty string")
}
if v != version {
t.Errorf("GetVersion() = %q, want %q", v, version)
}
}
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/UserAd/AgentMail

go 1.24.7

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.9 // indirect
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Loading