Skip to content

Commit c9bcb22

Browse files
committed
feat: Add init, build and deploy cmd to kagent to scaffold, build and deploy agent projects to kubernetes
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
1 parent a9cc8a1 commit c9bcb22

File tree

16 files changed

+1375
-3
lines changed

16 files changed

+1375
-3
lines changed

go/cli/cmd/kagent/main.go

Lines changed: 300 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import (
44
"context"
55
"fmt"
66
"os"
7-
"syscall"
8-
"strings"
97
"os/signal"
8+
"strings"
9+
"syscall"
10+
11+
"strconv"
1012

1113
"github.com/abiosoft/ishell/v2"
1214
"github.com/kagent-dev/kagent/go/cli/internal/cli"
@@ -216,7 +218,166 @@ func main() {
216218

217219
getCmd.AddCommand(getSessionCmd, getAgentCmd, getToolCmd)
218220

219-
rootCmd.AddCommand(installCmd, uninstallCmd, invokeCmd, bugReportCmd, versionCmd, dashboardCmd, getCmd)
221+
initCmd := &cobra.Command{
222+
Use: "init [framework] [language] [agent-name]",
223+
Short: "Initialize a new agent project",
224+
Long: `Initialize a new agent project using the specified framework and language.
225+
226+
You can customize the root agent instructions using the --instruction-file flag.
227+
You can select a specific model using --model-provider and --model-name flags.
228+
If no custom instruction file is provided, a default dice-rolling instruction will be used.
229+
If no model is specified, the agent will need to be configured later.
230+
231+
Examples:
232+
kagent init adk python dice
233+
kagent init adk python dice --instruction-file instructions.md
234+
kagent init adk python dice --model-provider Gemini --model-name gemini-2.0-flash`,
235+
Args: cobra.ExactArgs(3),
236+
Run: func(cmd *cobra.Command, args []string) {
237+
initCfg := &cli.InitCfg{
238+
Config: cfg,
239+
Framework: args[0],
240+
Language: args[1],
241+
AgentName: args[2],
242+
}
243+
244+
// Get instruction file path if specified
245+
if instructionFile, _ := cmd.Flags().GetString("instruction-file"); instructionFile != "" {
246+
initCfg.InstructionFile = instructionFile
247+
}
248+
249+
// Get model provider and name if specified
250+
if modelProvider, _ := cmd.Flags().GetString("model-provider"); modelProvider != "" {
251+
initCfg.ModelProvider = modelProvider
252+
}
253+
254+
if modelName, _ := cmd.Flags().GetString("model-name"); modelName != "" {
255+
initCfg.ModelName = modelName
256+
}
257+
258+
if description, _ := cmd.Flags().GetString("description"); description != "" {
259+
initCfg.Description = description
260+
}
261+
262+
if err := cli.InitCmd(initCfg); err != nil {
263+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
264+
os.Exit(1)
265+
}
266+
},
267+
Example: `kagent init adk python dice`,
268+
}
269+
270+
// Add flags for custom instructions and model selection
271+
initCmd.Flags().String("instruction-file", "", "Path to file containing custom instructions for the root agent")
272+
initCmd.Flags().String("model-provider", "", "Model provider (OpenAI, Anthropic, Ollama, Gemini)")
273+
initCmd.Flags().String("model-name", "", "Model name (e.g., gpt-4, claude-3-5-sonnet, gemini-2.0-flash)")
274+
initCmd.Flags().String("description", "", "Description for the agent")
275+
276+
buildCmd := &cobra.Command{
277+
Use: "build [project-directory]",
278+
Short: "Build a Docker image for an agent project",
279+
Long: `Build a Docker image for an agent project created with the init command.
280+
281+
This command will look for a Dockerfile in the specified project directory and build
282+
a Docker image using docker build. The image can optionally be pushed to a registry.
283+
284+
Image naming:
285+
- If --image is provided, it will be used as the full image specification (e.g., ghcr.io/myorg/my-agent:v1.0.0)
286+
- Otherwise, defaults to localhost:5001/{agentName}:latest where agentName is loaded from kagent.yaml
287+
288+
Examples:
289+
kagent build ./my-agent
290+
kagent build ./my-agent --image ghcr.io/myorg/my-agent:v1.0.0
291+
kagent build ./my-agent --image ghcr.io/myorg/my-agent:v1.0.0 --push`,
292+
Args: cobra.ExactArgs(1),
293+
Run: func(cmd *cobra.Command, args []string) {
294+
buildCfg := &cli.BuildCfg{
295+
Config: cfg,
296+
}
297+
298+
// Get project directory from positional argument
299+
buildCfg.ProjectDir = args[0]
300+
301+
if image, _ := cmd.Flags().GetString("image"); image != "" {
302+
buildCfg.Image = image
303+
}
304+
305+
if push, _ := cmd.Flags().GetBool("push"); push {
306+
buildCfg.Push = true
307+
}
308+
309+
if err := cli.BuildCmd(buildCfg); err != nil {
310+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
311+
os.Exit(1)
312+
}
313+
},
314+
Example: `kagent build ./my-agent`,
315+
}
316+
317+
// Add flags for build command
318+
buildCmd.Flags().String("image", "", "Full image specification (e.g., ghcr.io/myorg/my-agent:v1.0.0)")
319+
buildCmd.Flags().Bool("push", false, "Push the image to the registry")
320+
321+
deployCmd := &cobra.Command{
322+
Use: "deploy [project-directory]",
323+
Short: "Deploy an agent to Kubernetes",
324+
Long: `Deploy an agent to Kubernetes.
325+
326+
This command will read the kagent.yaml file from the specified project directory,
327+
create or reference a Kubernetes secret with the API key, and create an Agent CRD.
328+
329+
The command will:
330+
1. Load the agent configuration from kagent.yaml
331+
2. Either create a new secret with the provided API key or verify an existing secret
332+
3. Create an Agent CRD with the appropriate configuration
333+
334+
API Key Options:
335+
--api-key: Convenience option to create a new secret with the provided API key
336+
--api-key-secret: Canonical way to reference an existing secret by name
337+
338+
Examples:
339+
kagent deploy ./my-agent --api-key-secret "my-existing-secret"
340+
kagent deploy ./my-agent --api-key "your-api-key-here" --image "myregistry/myagent:v1.0"
341+
kagent deploy ./my-agent --api-key-secret "my-secret" --namespace "my-namespace"`,
342+
Args: cobra.ExactArgs(1),
343+
Run: func(cmd *cobra.Command, args []string) {
344+
deployCfg := &cli.DeployCfg{
345+
Config: cfg,
346+
}
347+
348+
// Get project directory from positional argument
349+
deployCfg.ProjectDir = args[0]
350+
351+
if image, _ := cmd.Flags().GetString("image"); image != "" {
352+
deployCfg.Image = image
353+
}
354+
355+
if apiKey, _ := cmd.Flags().GetString("api-key"); apiKey != "" {
356+
deployCfg.APIKey = apiKey
357+
}
358+
359+
if apiKeySecret, _ := cmd.Flags().GetString("api-key-secret"); apiKeySecret != "" {
360+
deployCfg.APIKeySecret = apiKeySecret
361+
}
362+
363+
if namespace, _ := cmd.Flags().GetString("namespace"); namespace != "" {
364+
deployCfg.Config.Namespace = namespace
365+
}
366+
367+
if err := cli.DeployCmd(deployCfg); err != nil {
368+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
369+
os.Exit(1)
370+
}
371+
},
372+
Example: `kagent deploy ./my-agent --api-key-secret "my-existing-secret"`,
373+
}
374+
375+
// Add flags for deploy command
376+
deployCmd.Flags().StringP("image", "i", "", "Image to use (defaults to localhost:5001/{agentName}:latest)")
377+
deployCmd.Flags().String("api-key", "", "API key for the model provider (convenience option to create secret)")
378+
deployCmd.Flags().String("api-key-secret", "", "Name of existing secret containing API key")
379+
380+
rootCmd.AddCommand(installCmd, uninstallCmd, invokeCmd, bugReportCmd, versionCmd, dashboardCmd, getCmd, initCmd, buildCmd, deployCmd)
220381

221382
// Initialize config
222383
if err := config.Init(); err != nil {
@@ -501,6 +662,142 @@ Example:
501662
},
502663
})
503664

665+
shell.AddCmd(&ishell.Cmd{
666+
Name: "init",
667+
Aliases: []string{"i"},
668+
Help: "Initialize a new kagent project.",
669+
LongHelp: `Initialize a new kagent project with the specified framework and language.
670+
671+
Usage: init [framework] [language] [project-name]
672+
673+
You can optionally provide custom instructions for the root agent by specifying a file path containing the instructions.
674+
You can also select a specific model provider and model name during initialization.
675+
If no custom instruction file is provided, a default dice-rolling instruction will be used.
676+
If no model is specified, the gemini-2.0-flash model will be used.
677+
678+
Examples:
679+
init adk python dice
680+
`,
681+
Func: func(c *ishell.Context) {
682+
if len(c.Args) != 3 {
683+
c.Println("Usage: init [framework] [language] [project-name]")
684+
c.Println("Example: init adk python dice")
685+
return
686+
}
687+
688+
initCfg := &cli.InitCfg{
689+
Framework: c.Args[0],
690+
Language: c.Args[1],
691+
AgentName: c.Args[2],
692+
}
693+
694+
// Prompt for custom instruction if user wants to provide one
695+
c.Println("Would you like to provide custom instructions for the root agent? (y/n)")
696+
choice := c.ReadLine()
697+
if strings.ToLower(strings.TrimSpace(choice)) == "y" {
698+
c.Print("Enter the path to your instruction file: ")
699+
filePath := c.ReadLine()
700+
if filePath != "" {
701+
initCfg.InstructionFile = strings.TrimSpace(filePath)
702+
c.Println("Instruction file path saved.")
703+
}
704+
}
705+
706+
// Prompt for model selection
707+
c.Println("Would you like to select a specific model? (y/n)")
708+
choice = c.ReadLine()
709+
if strings.ToLower(strings.TrimSpace(choice)) == "y" {
710+
c.Println("Available model providers:")
711+
c.Println("1. OpenAI")
712+
c.Println("2. Anthropic")
713+
c.Println("3. AzureOpenAI")
714+
c.Println("4. Ollama")
715+
c.Println("5. Gemini")
716+
c.Println("6. GeminiVertexAI")
717+
c.Println("7. AnthropicVertexAI")
718+
719+
c.Print("Enter the number of your choice (1-7): ")
720+
providerChoice := c.ReadLine()
721+
722+
providers := []string{"OpenAI", "Anthropic", "AzureOpenAI", "Ollama", "Gemini", "GeminiVertexAI", "AnthropicVertexAI"}
723+
if idx := strings.TrimSpace(providerChoice); idx != "" {
724+
if i, err := strconv.Atoi(idx); err == nil && i >= 1 && i <= len(providers) {
725+
initCfg.ModelProvider = providers[i-1]
726+
c.Printf("Selected provider: %s\n", initCfg.ModelProvider)
727+
728+
// Prompt for custom instruction if user wants to provide one
729+
c.Print("Enter the model name (e.g., gpt-4, claude-3-5-sonnet, gemini-2.0-flash): ")
730+
modelName := c.ReadLine()
731+
if modelName != "" {
732+
initCfg.ModelName = strings.TrimSpace(modelName)
733+
c.Printf("Selected model: %s\n", initCfg.ModelName)
734+
}
735+
}
736+
}
737+
}
738+
739+
if err := cli.InitCmd(initCfg); err != nil {
740+
c.Println("Error:", err)
741+
return
742+
}
743+
},
744+
})
745+
746+
shell.AddCmd(&ishell.Cmd{
747+
Name: "build",
748+
Aliases: []string{"b"},
749+
Help: "Build a Docker image for an agent project.",
750+
LongHelp: `Build a Docker image for an agent project created with the init command.
751+
752+
Usage: build [project-directory]
753+
754+
This command will look for a Dockerfile in the specified project directory and build
755+
a Docker image using docker build. The image can optionally be pushed to a registry.
756+
757+
Examples:
758+
build ./my-agent
759+
build ./my-agent --image ghcr.io/myorg/my-agent:v1.0.0
760+
build ./my-agent --image ghcr.io/myorg/my-agent:v1.0.0 --push
761+
`,
762+
Func: func(c *ishell.Context) {
763+
if len(c.Args) < 1 {
764+
c.Println("Usage: build [project-directory]")
765+
c.Println("Example: build ./my-agent")
766+
return
767+
}
768+
769+
buildCfg := &cli.BuildCfg{
770+
ProjectDir: c.Args[0],
771+
Config: cfg,
772+
}
773+
774+
// Prompt for full image specification if user wants to specify one
775+
c.Println("Would you like to specify a full image name? (y/n)")
776+
choice := c.ReadLine()
777+
if strings.ToLower(strings.TrimSpace(choice)) == "y" {
778+
c.Print("Enter the full image specification (e.g., ghcr.io/myorg/my-agent:v1.0.0): ")
779+
image := c.ReadLine()
780+
if image != "" {
781+
buildCfg.Image = strings.TrimSpace(image)
782+
c.Printf("Image set to: %s\n", buildCfg.Image)
783+
}
784+
}
785+
786+
// Prompt for push
787+
c.Println("Would you like to push the image after building? (y/n)")
788+
choice = c.ReadLine()
789+
if strings.ToLower(strings.TrimSpace(choice)) == "y" {
790+
buildCfg.Push = true
791+
c.Println("Image will be pushed after building.")
792+
}
793+
794+
if err := cli.BuildCmd(buildCfg); err != nil {
795+
c.Println("Error:", err)
796+
return
797+
}
798+
},
799+
})
800+
504801
defer func() {
505802
if pf := shell.Get(portForwardKey); pf != nil {
506803
pf.(*cli.PortForward).Stop()

0 commit comments

Comments
 (0)