Skip to content
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
119 changes: 118 additions & 1 deletion go/cli/cmd/kagent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,124 @@ func main() {

getCmd.AddCommand(getSessionCmd, getAgentCmd, getToolCmd)

rootCmd.AddCommand(installCmd, uninstallCmd, invokeCmd, bugReportCmd, versionCmd, dashboardCmd, getCmd)
initCfg := &cli.InitCfg{
Config: cfg,
}

initCmd := &cobra.Command{
Use: "init [framework] [language] [agent-name]",
Short: "Initialize a new agent project",
Long: `Initialize a new agent project using the specified framework and language.

You can customize the root agent instructions using the --instruction-file flag.
You can select a specific model using --model-provider and --model-name flags.
If no custom instruction file is provided, a default dice-rolling instruction will be used.
If no model is specified, the agent will need to be configured later.

Examples:
kagent init adk python dice
kagent init adk python dice --instruction-file instructions.md
kagent init adk python dice --model-provider Gemini --model-name gemini-2.0-flash`,
Args: cobra.ExactArgs(3),
Run: func(cmd *cobra.Command, args []string) {
initCfg.Framework = args[0]
initCfg.Language = args[1]
initCfg.AgentName = args[2]

if err := cli.InitCmd(initCfg); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
},
Example: `kagent init adk python dice`,
}

// Add flags for custom instructions and model selection
initCmd.Flags().StringVar(&initCfg.InstructionFile, "instruction-file", "", "Path to file containing custom instructions for the root agent")
initCmd.Flags().StringVar(&initCfg.ModelProvider, "model-provider", "Gemini", "Model provider (OpenAI, Anthropic, Gemini)")
initCmd.Flags().StringVar(&initCfg.ModelName, "model-name", "gemini-2.0-flash", "Model name (e.g., gpt-4, claude-3-5-sonnet, gemini-2.0-flash)")
initCmd.Flags().StringVar(&initCfg.Description, "description", "", "Description for the agent")

buildCfg := &cli.BuildCfg{
Config: cfg,
}

buildCmd := &cobra.Command{
Use: "build [project-directory]",
Short: "Build a Docker image for an agent project",
Long: `Build a Docker image for an agent project created with the init command.

This command will look for a Dockerfile in the specified project directory and build
a Docker image using docker build. The image can optionally be pushed to a registry.

Image naming:
- If --image is provided, it will be used as the full image specification (e.g., ghcr.io/myorg/my-agent:v1.0.0)
- Otherwise, defaults to localhost:5001/{agentName}:latest where agentName is loaded from kagent.yaml

Examples:
kagent build ./my-agent
kagent build ./my-agent --image ghcr.io/myorg/my-agent:v1.0.0
kagent build ./my-agent --image ghcr.io/myorg/my-agent:v1.0.0 --push`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
buildCfg.ProjectDir = args[0]

if err := cli.BuildCmd(buildCfg); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
},
Example: `kagent build ./my-agent`,
}

// Add flags for build command
buildCmd.Flags().StringVar(&buildCfg.Image, "image", "", "Full image specification (e.g., ghcr.io/myorg/my-agent:v1.0.0)")
buildCmd.Flags().BoolVar(&buildCfg.Push, "push", false, "Push the image to the registry")

deployCfg := &cli.DeployCfg{
Config: cfg,
}

deployCmd := &cobra.Command{
Use: "deploy [project-directory]",
Short: "Deploy an agent to Kubernetes",
Long: `Deploy an agent to Kubernetes.

This command will read the kagent.yaml file from the specified project directory,
create or reference a Kubernetes secret with the API key, and create an Agent CRD.

The command will:
1. Load the agent configuration from kagent.yaml
2. Either create a new secret with the provided API key or verify an existing secret
3. Create an Agent CRD with the appropriate configuration

API Key Options:
--api-key: Convenience option to create a new secret with the provided API key
--api-key-secret: Canonical way to reference an existing secret by name

Examples:
kagent deploy ./my-agent --api-key-secret "my-existing-secret"
kagent deploy ./my-agent --api-key "your-api-key-here" --image "myregistry/myagent:v1.0"
kagent deploy ./my-agent --api-key-secret "my-secret" --namespace "my-namespace"`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
deployCfg.ProjectDir = args[0]

if err := cli.DeployCmd(ctx, deployCfg); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
},
Example: `kagent deploy ./my-agent --api-key-secret "my-existing-secret"`,
}

// Add flags for deploy command
deployCmd.Flags().StringVarP(&deployCfg.Image, "image", "i", "", "Image to use (defaults to localhost:5001/{agentName}:latest)")
deployCmd.Flags().StringVar(&deployCfg.APIKey, "api-key", "", "API key for the model provider (convenience option to create secret)")
deployCmd.Flags().StringVar(&deployCfg.APIKeySecret, "api-key-secret", "", "Name of existing secret containing API key")
deployCmd.Flags().StringVar(&deployCfg.Config.Namespace, "namespace", "", "Kubernetes namespace to deploy to")

rootCmd.AddCommand(installCmd, uninstallCmd, invokeCmd, bugReportCmd, versionCmd, dashboardCmd, getCmd, initCmd, buildCmd, deployCmd)

// Initialize config
if err := config.Init(); err != nil {
Expand Down
167 changes: 167 additions & 0 deletions go/cli/internal/cli/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package cli

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/kagent-dev/kagent/go/cli/internal/config"
"github.com/kagent-dev/kagent/go/cli/internal/frameworks/common"
)

type BuildCfg struct {
ProjectDir string
Image string
Push bool
Config *config.Config
}

// BuildCmd builds a Docker image for an agent project
func BuildCmd(cfg *BuildCfg) error {
// Validate project directory
if cfg.ProjectDir == "" {
return fmt.Errorf("project directory is required")
}

// Check if project directory exists
if _, err := os.Stat(cfg.ProjectDir); os.IsNotExist(err) {
return fmt.Errorf("project directory does not exist: %s", cfg.ProjectDir)
}

// Check if Dockerfile exists in project directory
dockerfilePath := filepath.Join(cfg.ProjectDir, "Dockerfile")
if _, err := os.Stat(dockerfilePath); os.IsNotExist(err) {
return fmt.Errorf("dockerfile not found in project directory: %s", dockerfilePath)
}

// Check if Docker is available and running
if err := checkDockerAvailability(); err != nil {
return fmt.Errorf("docker check failed: %v", err)
}

// Build the Docker image
if err := buildDockerImage(cfg); err != nil {
return fmt.Errorf("failed to build Docker image: %v", err)
}

// Push the image if requested
if cfg.Push {
// Docker availability is already checked above, but we could add another check here if needed
if err := pushDockerImage(cfg); err != nil {
return fmt.Errorf("failed to push Docker image: %v", err)
}
}

return nil
}

// buildDockerImage builds the Docker image using docker build
func buildDockerImage(cfg *BuildCfg) error {
// Construct the image name
imageName := constructImageName(cfg)

// Build command arguments
args := []string{"build", "-t", imageName, "."}

// Execute docker build command
cmd := exec.Command("docker", args...)
cmd.Dir = cfg.ProjectDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if cfg.Config.Verbose {
fmt.Printf("Executing: docker %s\n", strings.Join(args, " "))
fmt.Printf("Working directory: %s\n", cmd.Dir)
}

if err := cmd.Run(); err != nil {
return fmt.Errorf("docker build failed: %v", err)
}

fmt.Printf("Successfully built Docker image: %s\n", imageName)
return nil
}

// pushDockerImage pushes the Docker image to the specified registry
func pushDockerImage(cfg *BuildCfg) error {
// Construct the image name
imageName := constructImageName(cfg)

// Execute docker push command
cmd := exec.Command("docker", "push", imageName)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if cfg.Config.Verbose {
fmt.Printf("Executing: docker push %s\n", imageName)
}

if err := cmd.Run(); err != nil {
return fmt.Errorf("docker push failed: %v", err)
}

fmt.Printf("Successfully pushed Docker image: %s\n", imageName)
return nil
}

// constructImageName constructs the full image name from the provided image or defaults
func constructImageName(cfg *BuildCfg) string {
// If a full image specification is provided, use it as-is
if cfg.Image != "" {
return cfg.Image
}

// Otherwise, construct from defaults
// Get agent name from kagent.yaml file
agentName := getAgentNameFromManifest(cfg.ProjectDir)

// If no agent name found in manifest, fall back to directory name
if agentName == "" {
agentName = filepath.Base(cfg.ProjectDir)
}

// Use default registry and tag
registry := "localhost:5001"
tag := "latest"

// Construct full image name: registry/agent-name:tag
return fmt.Sprintf("%s/%s:%s", registry, agentName, tag)
}

// getAgentNameFromManifest attempts to load the agent name from kagent.yaml
func getAgentNameFromManifest(projectDir string) string {
// Use the Manager to load the manifest
manager := common.NewManifestManager(projectDir)
manifest, err := manager.Load()
if err != nil {
// Silently fail and return empty string to fall back to directory name
return ""
}

return manifest.Name
}

// checkDockerAvailability checks if Docker is installed and running
func checkDockerAvailability() error {
// Check if docker command exists
if _, err := exec.LookPath("docker"); err != nil {
return fmt.Errorf("docker command not found in PATH. Please install Docker")
}

// Check if Docker daemon is running by running docker version
cmd := exec.Command("docker", "version", "--format", "{{.Server.Version}}")
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("docker daemon is not running or not accessible. Please start Docker Desktop or Docker daemon")
}

// Check if we got a valid version string
version := strings.TrimSpace(string(output))
if version == "" {
return fmt.Errorf("docker daemon returned empty version. Docker may not be properly installed")
}

return nil
}
Loading
Loading