CLI Examples

Complete examples of CLI agents, dual-mode setups, and custom formatters

Example 1: Simple CLI-Only Agent

A basic CLI tool with no control plane dependency:

main.go
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/Agent-Field/agentfield/sdk/go/agent"
)

func main() {
    a, err := agent.New(agent.Config{
        NodeID: "greeter",
        CLIConfig: &agent.CLIConfig{
            AppName:        "greeter",
            AppDescription: "Simple greeting tool",
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    a.RegisterReasoner("greet", func(ctx context.Context, input map[string]any) (any, error) {
        name, ok := input["name"].(string)
        if !ok {
            return nil, fmt.Errorf("name is required")
        }

        return fmt.Sprintf("Hello, %s!", name), nil
    },
        agent.WithCLI(),
        agent.WithDefaultCLI(),
        agent.WithDescription("Greet a user by name"),
    )

    if err := a.Run(context.Background()); err != nil {
        log.Fatal(err)
    }
}

Build and use:

# Build
go build -o greeter

# Use
./greeter --set name=Alice
# Output: Hello, Alice!

# Help
./greeter --help

Example 2: Dual-Mode Agent (CLI + Server)

Same binary runs as CLI tool or control plane agent:

main.go
package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/Agent-Field/agentfield/sdk/go/agent"
)

func main() {
    a, err := agent.New(agent.Config{
        NodeID:        "processor",
        Version:       "1.0.0",
        AgentFieldURL: os.Getenv("AGENTFIELD_URL"), // Optional

        CLIConfig: &agent.CLIConfig{
            AppName:        "processor",
            AppDescription: "Data processor with control plane integration",
            HelpEpilog: `Examples:
  # CLI mode
  $ ./processor --set input="data"

  # Server mode
  $ AGENTFIELD_URL=http://localhost:8080 ./processor serve`,
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    a.RegisterReasoner("process", func(ctx context.Context, input map[string]any) (any, error) {
        data := input["input"].(string)

        result := map[string]any{
            "processed": fmt.Sprintf("Processed: %s", data),
            "length":    len(data),
        }

        return result, nil
    },
        agent.WithCLI(),
        agent.WithDefaultCLI(),
        agent.WithDescription("Process input data"),
    )

    if err := a.Run(context.Background()); err != nil {
        log.Fatal(err)
    }
}

Usage:

# Build once
go build -o processor

# CLI mode (standalone)
./processor --set input="hello world"

# Server mode (with control plane)
AGENTFIELD_URL=http://localhost:8080 ./processor serve

Example 3: Multiple CLI Commands

Agent with several CLI commands:

main.go
package main

import (
    "context"
    "fmt"
    "log"
    "strings"

    "github.com/Agent-Field/agentfield/sdk/go/agent"
)

func main() {
    a, err := agent.New(agent.Config{
        NodeID: "text-tools",
        CLIConfig: &agent.CLIConfig{
            AppName:        "text-tools",
            AppDescription: "Text processing utilities",
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    // Default command: analyze
    a.RegisterReasoner("analyze", func(ctx context.Context, input map[string]any) (any, error) {
        text := input["text"].(string)
        return map[string]any{
            "length": len(text),
            "words":  len(strings.Fields(text)),
            "lines":  len(strings.Split(text, "\n")),
        }, nil
    },
        agent.WithCLI(),
        agent.WithDefaultCLI(),
        agent.WithDescription("Analyze text statistics"),
    )

    // Uppercase command
    a.RegisterReasoner("uppercase", func(ctx context.Context, input map[string]any) (any, error) {
        text := input["text"].(string)
        return strings.ToUpper(text), nil
    },
        agent.WithCLI(),
        agent.WithDescription("Convert text to uppercase"),
    )

    // Reverse command
    a.RegisterReasoner("reverse", func(ctx context.Context, input map[string]any) (any, error) {
        text := input["text"].(string)
        runes := []rune(text)
        for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
            runes[i], runes[j] = runes[j], runes[i]
        }
        return string(runes), nil
    },
        agent.WithCLI(),
        agent.WithDescription("Reverse text"),
    )

    if err := a.Run(context.Background()); err != nil {
        log.Fatal(err)
    }
}

Usage:

# Default command (analyze)
./text-tools --set text="Hello World"

# Explicit commands
./text-tools uppercase --set text="hello"
./text-tools reverse --set text="hello"

# List commands
./text-tools --help

Example 4: Custom Formatter

Pretty output with custom formatting:

main.go
package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/Agent-Field/agentfield/sdk/go/agent"
    "github.com/fatih/color"
)

func main() {
    a, err := agent.New(agent.Config{
        NodeID: "analyzer",
        CLIConfig: &agent.CLIConfig{
            AppName:             "analyzer",
            AppDescription:      "Document analyzer",
            DefaultOutputFormat: "text",
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    a.RegisterReasoner("analyze", func(ctx context.Context, input map[string]any) (any, error) {
        text := input["text"].(string)

        sentiment := "positive"
        if len(text) < 10 {
            sentiment = "neutral"
        }

        return map[string]any{
            "sentiment":  sentiment,
            "confidence": 0.85,
            "length":     len(text),
            "topics":     []string{"technology", "innovation"},
        }, nil
    },
        agent.WithCLI(),
        agent.WithDefaultCLI(),
        agent.WithCLIFormatter(func(ctx context.Context, result any, err error) {
            if err != nil {
                color.Red("❌ Error: %v", err)
                os.Exit(1)
                return
            }

            data := result.(map[string]any)

            fmt.Println()
            color.Cyan("📊 Analysis Results")
            color.Cyan("══════════════════")
            fmt.Println()

            // Sentiment with color
            sentiment := data["sentiment"].(string)
            fmt.Print("Sentiment: ")
            switch sentiment {
            case "positive":
                color.Green(sentiment)
            case "negative":
                color.Red(sentiment)
            default:
                color.Yellow(sentiment)
            }

            // Confidence
            confidence := data["confidence"].(float64)
            fmt.Printf("Confidence: %.1f%%\n", confidence*100)

            // Length
            length := data["length"].(int)
            fmt.Printf("Length: %d characters\n", length)

            // Topics
            topics := data["topics"].([]string)
            fmt.Printf("Topics: %v\n", topics)
            fmt.Println()
        }),
    )

    if err := a.Run(context.Background()); err != nil {
        log.Fatal(err)
    }
}

Output:

📊 Analysis Results
══════════════════

Sentiment: positive
Confidence: 85.0%
Length: 156 characters
Topics: [technology innovation]

Example 5: AI-Powered CLI Tool

CLI tool with AI capabilities:

main.go
package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/Agent-Field/agentfield/sdk/go/agent"
    "github.com/Agent-Field/agentfield/sdk/go/ai"
)

func main() {
    a, err := agent.New(agent.Config{
        NodeID: "ai-assistant",
        AIConfig: &ai.Config{
            Provider: "openai",
            APIKey:   os.Getenv("OPENAI_API_KEY"),
            Model:    "gpt-4",
        },
        CLIConfig: &agent.CLIConfig{
            AppName:        "ai-assistant",
            AppDescription: "AI-powered CLI assistant",
            HelpPreamble:   "⚠️  Set OPENAI_API_KEY before running",
            EnvironmentVars: []string{
                "OPENAI_API_KEY (required) - Your OpenAI API key",
            },
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    a.RegisterReasoner("ask", func(ctx context.Context, input map[string]any) (any, error) {
        question := input["question"].(string)

        // Use AI client
        resp, err := a.AI(ctx, question)
        if err != nil {
            return nil, fmt.Errorf("AI request failed: %w", err)
        }

        return resp.Choices[0].Message.Content, nil
    },
        agent.WithCLI(),
        agent.WithDefaultCLI(),
        agent.WithDescription("Ask the AI a question"),
        agent.WithCLIFormatter(func(ctx context.Context, result any, err error) {
            if err != nil {
                fmt.Fprintf(os.Stderr, "Error: %v\n", err)
                os.Exit(1)
                return
            }

            fmt.Println(result)
        }),
    )

    if err := a.Run(context.Background()); err != nil {
        log.Fatal(err)
    }
}

Usage:

export OPENAI_API_KEY=sk-...

./ai-assistant --set question="Explain quantum computing"

Example 6: File Processing

Process files with progress indication:

main.go
package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/Agent-Field/agentfield/sdk/go/agent"
)

func main() {
    a, err := agent.New(agent.Config{
        NodeID: "file-processor",
        CLIConfig: &agent.CLIConfig{
            AppName:        "file-processor",
            AppDescription: "Process files with AI",
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    a.RegisterReasoner("process", func(ctx context.Context, input map[string]any) (any, error) {
        filePath := input["file"].(string)

        // Read file
        data, err := os.ReadFile(filePath)
        if err != nil {
            return nil, fmt.Errorf("failed to read file: %w", err)
        }

        // Process
        result := map[string]any{
            "file":  filePath,
            "size":  len(data),
            "lines": len(data),
        }

        return result, nil
    },
        agent.WithCLI(),
        agent.WithDefaultCLI(),
        agent.WithDescription("Process a file"),
    )

    if err := a.Run(context.Background()); err != nil {
        log.Fatal(err)
    }
}

Usage:

./file-processor --set file=document.txt

Example 7: JSON Input/Output

Machine-readable CLI for pipelines:

main.go
package main

import (
    "context"
    "fmt"
    "log"
    "strings"

    "github.com/Agent-Field/agentfield/sdk/go/agent"
)

func main() {
    a, err := agent.New(agent.Config{
        NodeID: "filter",
        CLIConfig: &agent.CLIConfig{
            AppName:             "filter",
            AppDescription:      "Filter and transform JSON data",
            DefaultOutputFormat: "json",
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    a.RegisterReasoner("filter", func(ctx context.Context, input map[string]any) (any, error) {
        items := input["items"].([]interface{})
        minLength := 5

        if ml, ok := input["min_length"].(string); ok {
            fmt.Sscanf(ml, "%d", &minLength)
        }

        var filtered []string
        for _, item := range items {
            str := fmt.Sprintf("%v", item)
            if len(str) >= minLength {
                filtered = append(filtered, str)
            }
        }

        return filtered, nil
    },
        agent.WithCLI(),
        agent.WithDefaultCLI(),
    )

    if err := a.Run(context.Background()); err != nil {
        log.Fatal(err)
    }
}

Usage in pipeline:

# data.json
{
  "items": ["a", "hello", "world", "go", "agent"]
}

# Filter and process
cat data.json | ./filter --set min_length=4 | jq '.[]'

Example 8: Graceful Degradation

Agent that adapts to control plane availability:

main.go
package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/Agent-Field/agentfield/sdk/go/agent"
)

func main() {
    a, err := agent.New(agent.Config{
        NodeID:        "smart-agent",
        AgentFieldURL: os.Getenv("AGENTFIELD_URL"),
        CLIConfig: &agent.CLIConfig{
            AppName:        "smart-agent",
            AppDescription: "Adaptive agent (works with or without control plane)",
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    hasControlPlane := a.Config.AgentFieldURL != ""

    a.RegisterReasoner("process", func(ctx context.Context, input map[string]any) (any, error) {
        data := input["data"].(string)

        if hasControlPlane {
            // Try to use control plane features
            res, err := a.Call(ctx, "nlp-agent.analyze", map[string]any{
                "text": data,
            })
            if err == nil {
                return res, nil
            }

            // Fallback to local if control plane call fails
            log.Printf("Control plane call failed, using local processing: %v", err)
        }

        // Local processing
        return map[string]any{
            "processed": fmt.Sprintf("Locally processed: %s", data),
            "mode":      "standalone",
        }, nil
    },
        agent.WithCLI(),
        agent.WithDefaultCLI(),
    )

    if err := a.Run(context.Background()); err != nil {
        log.Fatal(err)
    }
}

Usage:

# Standalone
./smart-agent --set data="hello"

# With control plane
AGENTFIELD_URL=http://localhost:8080 ./smart-agent --set data="hello"

Building and Distribution

Local Build

go build -o my-agent
./my-agent --help

Multi-Platform Build

# Linux
GOOS=linux GOARCH=amd64 go build -o my-agent-linux-amd64

# macOS (Intel)
GOOS=darwin GOARCH=amd64 go build -o my-agent-darwin-amd64

# macOS (Apple Silicon)
GOOS=darwin GOARCH=arm64 go build -o my-agent-darwin-arm64

# Windows
GOOS=windows GOARCH=amd64 go build -o my-agent-windows-amd64.exe

With GoReleaser

.goreleaser.yml
builds:
  - id: my-agent
    binary: my-agent
    main: ./main.go
    goos:
      - linux
      - darwin
      - windows
    goarch:
      - amd64
      - arm64

archives:
  - format: tar.gz
    name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
goreleaser release --snapshot --clean

Next Steps