Skip to content

Commit fe93a40

Browse files
committed
feat(agents): improve agents CLI output with useful feedback
- Add clear feedback about detected project types - Add UV lock file validation warnings with guidance - Add helpful "Next steps" after Dockerfile generation - Improve error messages with multi-line formatting
1 parent cc0689e commit fe93a40

File tree

2 files changed

+54
-21
lines changed

2 files changed

+54
-21
lines changed

cmd/lk/agent.go

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {
382382
return fmt.Errorf("unable to determine project type: %w, please use a supported project type, or create your own Dockerfile in the current directory", err)
383383
}
384384

385-
if err := requireDockerfile(ctx, cmd, workingDir, projectType, settingsMap); err != nil {
385+
if err := requireDockerfile(ctx, cmd, workingDir, settingsMap); err != nil {
386386
return err
387387
}
388388

@@ -1118,29 +1118,19 @@ func requireSecrets(_ context.Context, cmd *cli.Command, required, lazy bool) ([
11181118
return secretsSlice, nil
11191119
}
11201120

1121-
func requireDockerfile(_ context.Context, cmd *cli.Command, workingDir string, projectType agentfs.ProjectType, settingsMap map[string]string) error {
1121+
func requireDockerfile(_ context.Context, cmd *cli.Command, workingDir string, settingsMap map[string]string) error {
11221122
dockerfileExists, err := agentfs.HasDockerfile(workingDir)
11231123
if err != nil {
11241124
return err
11251125
}
11261126

11271127
if !dockerfileExists {
11281128
if !cmd.Bool("silent") {
1129-
var innerErr error
1130-
if err := util.Await(
1131-
"Creating Dockerfile...",
1132-
func() {
1133-
innerErr = agentfs.CreateDockerfile(workingDir, projectType, settingsMap)
1134-
},
1135-
); err != nil {
1129+
if err := agentfs.CreateDockerfile(workingDir, settingsMap); err != nil {
11361130
return err
11371131
}
1138-
if innerErr != nil {
1139-
return innerErr
1140-
}
1141-
fmt.Println("Created [" + util.Accented("Dockerfile") + "]")
11421132
} else {
1143-
if err := agentfs.CreateDockerfile(workingDir, projectType, settingsMap); err != nil {
1133+
if err := agentfs.CreateDockerfile(workingDir, settingsMap); err != nil {
11441134
return err
11451135
}
11461136
}

pkg/agentfs/docker.go

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,45 +49,88 @@ func HasDockerfile(dir string) (bool, error) {
4949
return false, nil
5050
}
5151

52-
func CreateDockerfile(dir string, projectType ProjectType, settingsMap map[string]string) error {
52+
func CreateDockerfile(dir string, settingsMap map[string]string) error {
5353
if len(settingsMap) == 0 {
5454
return fmt.Errorf("unable to fetch client settings from server, please try again later")
5555
}
5656

57+
projectType, err := DetectProjectType(dir)
58+
if err != nil {
59+
return fmt.Errorf(`× Unable to determine project type
60+
61+
Supported project types:
62+
• Python: requires requirements.txt or pyproject.toml
63+
• Node.js: requires package.json
64+
65+
Please ensure your project has the appropriate dependency file, or create a Dockerfile manually in the current directory`)
66+
}
67+
68+
// Provide user feedback about detected project type
69+
switch projectType {
70+
case ProjectTypeNode:
71+
fmt.Printf("✔ Detected Node.js project (found %s)\n", util.Accented("package.json"))
72+
fmt.Printf(" Using template [%s] with npm/yarn support\n", util.Accented("node"))
73+
case ProjectTypePythonUV:
74+
fmt.Printf("✔ Detected Python project with UV package manager\n")
75+
fmt.Printf(" Using template [%s] for faster builds\n", util.Accented("python.uv"))
76+
// Validate UV project setup
77+
validateUVProject(dir)
78+
case ProjectTypePythonPip:
79+
fmt.Printf("✔ Detected Python project with pip package manager\n")
80+
fmt.Printf(" Using template [%s]\n", util.Accented("python.pip"))
81+
}
82+
5783
var dockerfileContent []byte
5884
var dockerIgnoreContent []byte
59-
var err error
6085

6186
dockerfileContent, err = fs.ReadFile("examples/" + string(projectType) + ".Dockerfile")
6287
if err != nil {
63-
return err
88+
return fmt.Errorf("failed to load Dockerfile template '%s': %w", string(projectType), err)
6489
}
90+
6591
dockerIgnoreContent, err = fs.ReadFile("examples/" + string(projectType) + ".dockerignore")
6692
if err != nil {
67-
return err
93+
return fmt.Errorf("failed to load .dockerignore template for '%s': %w", string(projectType), err)
6894
}
6995

7096
// TODO: (@rektdeckard) support Node entrypoint validation
7197
if projectType.IsPython() {
7298
dockerfileContent, err = validateEntrypoint(dir, dockerfileContent, dockerIgnoreContent, projectType, settingsMap)
7399
if err != nil {
74-
return err
100+
return fmt.Errorf("failed to validate Python entry point: %w", err)
75101
}
76102
}
77103

78104
err = os.WriteFile(filepath.Join(dir, "Dockerfile"), dockerfileContent, 0644)
79105
if err != nil {
80-
return err
106+
return fmt.Errorf("failed to write Dockerfile: %w", err)
81107
}
82108

83109
err = os.WriteFile(filepath.Join(dir, ".dockerignore"), dockerIgnoreContent, 0644)
84110
if err != nil {
85-
return err
111+
return fmt.Errorf("failed to write .dockerignore: %w", err)
86112
}
87113

114+
fmt.Printf("\n✔ Successfully generated Docker files:\n")
115+
fmt.Printf(" %s - Container build instructions\n", util.Accented("Dockerfile"))
116+
fmt.Printf(" %s - Files excluded from build context\n", util.Accented(".dockerignore"))
117+
fmt.Printf("\nNext steps:\n")
118+
fmt.Printf(" ► Review the %s and uncomment any needed system packages\n", util.Accented("Dockerfile"))
119+
fmt.Printf(" ► Build your agent: docker build -t my-agent .\n")
120+
fmt.Printf(" ► Test locally: docker run my-agent\n")
121+
88122
return nil
89123
}
90124

125+
func validateUVProject(dir string) {
126+
uvLockPath := filepath.Join(dir, "uv.lock")
127+
if _, err := os.Stat(uvLockPath); err != nil {
128+
fmt.Printf("! Warning: UV project detected but %s file not found\n", util.Accented("uv.lock"))
129+
fmt.Printf(" Consider running %s to generate %s for reproducible builds\n", util.Accented("uv lock"), util.Accented("uv.lock"))
130+
fmt.Printf(" This ensures consistent dependency versions across environments\n\n")
131+
}
132+
}
133+
91134
func validateEntrypoint(dir string, dockerfileContent []byte, dockerignoreContent []byte, projectType ProjectType, settingsMap map[string]string) ([]byte, error) {
92135
valFile := func(fileName string) (string, error) {
93136
// Parse dockerignore patterns to filter out files that won't be in build context

0 commit comments

Comments
 (0)