Skip to content

Commit 86bf503

Browse files
authored
Make the CLI pretty ✨ (#2793)
* feat: add ⚙ �� prefix to console output for visual distinction Add a prefix and separator to all console log messages so Cog's output is visually distinguishable from Docker build output and model output: - Info/Debug: dim ⚙ › prefix - Warn: bold yellow ⚠ › prefix - Error/Fatal: bold red ⅹ › prefix Blank lines at info/debug level print without prefix for clean spacing. Warn/error blank lines keep their prefix to maintain visual continuity in multi-line error messages. Output() (stdout, for model predictions) remains unstyled. * feat: add console.Bold() and highlight dynamic values in messages Add a Bold() helper to the console package that applies bold formatting when color is enabled. Use it to highlight key dynamic values (image names, paths, URLs, hostnames) in user-facing messages so they stand out within the dim-prefixed lines. Also remove single quotes around values where bold makes them visually distinct without needing quoting. * chore: demote noisy build messages to debug level Move 'Generating model schema...', 'Using local coglet wheel...', and 'Using local cog wheel...' from info to debug level. These are implementation details that clutter normal output but remain visible with --debug. * feat: add spacing between build/prediction output phases Add blank lines after 'Building Docker image...' (before Docker build output) and after 'Running prediction/training...' (before model output) to visually separate Cog's framework messages from external output. * feat: add console.Success() with green ✓ prefix via Style system Introduce a Style type that controls the icon/color of a log line independently from the log level. This allows Success to display at info level (same filtering) but with a green ✓ prefix. New styles can be added by extending the Style enum without touching the level hierarchy. Apply Success to: image built, cog init completion, login success, and image pushed messages. Drop the ✅ emoji from init since the green ✓ prefix replaces it. * feat: wrap long lines to terminal width with prefix on continuation lines When stderr is a TTY, wrap console messages to the terminal width so they don't overflow. Each continuation line gets the same prefix as the first line for visual continuity. Wrapping is ANSI-aware: escape codes are treated as zero-width so colored/bold text doesn't cause premature breaks. Lines break on word boundaries where possible, with hard breaks as a fallback. * feat: add InfoUnformatted for prefix-free interactive output Add InfoUnformatted/InfoUnformattedf methods that write to stderr at info level without any icon prefix. These are used for conversational and interactive output (login prompts, instructions) where the gear prefix would be visual noise. Long lines are wrapped to terminal width when stderr is a TTY, same as prefixed output. Apply to login flows in both Replicate and generic providers: replace console.Info with console.InfoUnformatted for all user-facing prompts and instructions. Rename 'CLI auth token:' prompt to 'Token:' and improve spacing around the prompt. * fix: improve error message wording and add spacing after run message - 'Failed to predict' → 'Failed to run prediction' in predict.go - 'The inputs you passed to cog predict' → 'The inputs you passed' in predictor.go (shorter, less redundant since the user knows they ran cog) - Add blank line after 'Running...' message in run.go for visual separation from Docker output * refactor: improve init command output style - Remove leading \n from 'Setting up...' message (prefix handles spacing), use separate console.Info('') for the blank line after - Show 'Creating {filename}' (info, before write) instead of 'Created {fullpath}' (success, after write) — less noisy, uses just the filename since the user knows their working directory * refactor: move build message and spacing to CLI layer The 'Building Docker image...' message and surrounding blank lines are user-facing concerns that belong in the CLI commands, not deep in pkg/image/build.go. This lets each command control what it shows: - cog build / cog push: show image name (user-chosen, meaningful) - cog predict / cog train / cog run / cog serve: hide image name (ephemeral internal detail the user can't use) Each CLI command now prints the message + blank line before calling resolver.Build(), and the pre-ImageBuild blank lines are removed from image/build.go. * fix: update integration tests for console output changes - build_cog_init: match 'Image built as' without 'cog-' prefix since the success prefix (✔) is now part of the line - wheel_resolution: use --debug flag since wheel resolution messages were demoted to debug level
1 parent caf6852 commit 86bf503

File tree

16 files changed

+285
-46
lines changed

16 files changed

+285
-46
lines changed

integration-tests/tests/build_cog_init.txtar

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ cog init
66

77
# Build the initialized project
88
cog build -t $TEST_IMAGE
9-
stderr 'Image built as cog-'
9+
stderr 'Image built as'
1010

1111
# Verify the expected files were created
1212
exists cog.yaml

integration-tests/tests/wheel_resolution.txtar

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
# Test 1: COG_SDK_WHEEL pointing to repo dist/ directory finds wheel
1212
env COG_SDK_WHEEL=$REPO_ROOT/dist
13-
cog build -t $TEST_IMAGE
13+
cog build --debug -t $TEST_IMAGE
1414
stderr 'Using local cog wheel:'
1515

1616
# Test 2: Relative path that doesn't exist gives clear error

pkg/cli/build.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,17 @@ func buildCommand(cmd *cobra.Command, args []string) error {
9191
imageName = config.DockerImageName(src.ProjectDir)
9292
}
9393

94+
console.Infof("Building Docker image from environment in cog.yaml as %s...", console.Bold(imageName))
95+
console.Info("")
96+
9497
resolver := model.NewResolver(dockerClient, registry.NewRegistryClient())
9598
m, err := resolver.Build(ctx, src, buildOptionsFromFlags(cmd, imageName, nil))
9699
if err != nil {
97100
return err
98101
}
99102

100-
console.Infof("\nImage built as %s", m.ImageRef())
103+
console.Info("")
104+
console.Successf("Image built as %s", console.Bold(m.ImageRef()))
101105

102106
return nil
103107
}

pkg/cli/init.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ and prediction interface. Edit them to match your model's requirements.`,
3737
}
3838

3939
func initCommand(cmd *cobra.Command, args []string) error {
40-
console.Infof("\nSetting up the current directory for use with Cog...\n")
40+
console.Info("Setting up the current directory for use with Cog...")
41+
console.Info("")
4142

4243
cwd, err := os.Getwd()
4344
if err != nil {
@@ -68,7 +69,7 @@ func initCommand(cmd *cobra.Command, args []string) error {
6869
}
6970
}
7071

71-
console.Infof("\nDone! For next steps, check out the docs at https://cog.run/getting-started")
72+
console.Successf("\nDone! For next steps, check out the docs at https://cog.run/getting-started")
7273

7374
return nil
7475
}
@@ -142,11 +143,11 @@ func processTemplateFile(fs embed.FS, templateDir, filename, cwd string) error {
142143
}
143144
}
144145

146+
console.Infof("Creating %s", console.Bold(filename))
147+
145148
if err := os.WriteFile(filePath, content, 0o644); err != nil {
146149
return fmt.Errorf("Error writing %s: %w", filePath, err)
147150
}
148-
149-
console.Infof("✅ Created %s", filePath)
150151
return nil
151152
}
152153

pkg/cli/predict.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ func cmdPredict(cmd *cobra.Command, args []string) error {
197197
return err
198198
}
199199

200+
console.Info("Building Docker image from environment in cog.yaml...")
201+
console.Info("")
200202
m, err := resolver.Build(ctx, src, serveBuildOptions(cmd))
201203
if err != nil {
202204
return err
@@ -237,7 +239,7 @@ func cmdPredict(cmd *cobra.Command, args []string) error {
237239
}
238240

239241
console.Info("")
240-
console.Infof("Starting Docker image %s and running setup()...", imageName)
242+
console.Info("Starting Docker image and running setup()...")
241243

242244
// Automatically propagate RUST_LOG for Rust coglet debugging
243245
env := envFlags
@@ -365,6 +367,7 @@ func runPrediction(predictor predict.Predictor, inputs predict.Inputs, outputPat
365367
} else {
366368
console.Info("Running prediction...")
367369
}
370+
console.Info("")
368371

369372
// Generate output depending on type in schema
370373
url := "/predictions"
@@ -398,7 +401,7 @@ func runPrediction(predictor predict.Predictor, inputs predict.Inputs, outputPat
398401

399402
prediction, err := predictor.Predict(inputs, context)
400403
if err != nil {
401-
return fmt.Errorf("Failed to predict: %w", err)
404+
return fmt.Errorf("Failed to run prediction: %w", err)
402405
}
403406

404407
schema, err := predictor.GetSchema()

pkg/cli/push.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ func push(cmd *cobra.Command, args []string) error {
9797
resolver := model.NewResolver(dockerClient, regClient)
9898

9999
// Build the model
100+
console.Infof("Building Docker image from environment in cog.yaml as %s...", console.Bold(imageName))
101+
console.Info("")
100102
buildOpts := buildOptionsFromFlags(cmd, imageName, annotations)
101103
m, err := resolver.Build(ctx, src, buildOpts)
102104
if err != nil {
@@ -112,7 +114,7 @@ func push(cmd *cobra.Command, args []string) error {
112114
}
113115

114116
// Push the model (image + optional weights)
115-
console.Infof("\nPushing image '%s'...", m.ImageRef())
117+
console.Infof("\nPushing image %s...", console.Bold(m.ImageRef()))
116118

117119
// Set up progress display using Docker's jsonmessage rendering. This uses the
118120
// same cursor movement and progress display as `docker push`, which handles

pkg/cli/run.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ func run(cmd *cobra.Command, args []string) error {
8181

8282
resolver := model.NewResolver(dockerClient, registry.NewRegistryClient())
8383

84+
console.Info("Building Docker image from environment in cog.yaml...")
85+
console.Info("")
8486
opts := serveBuildOptions(cmd)
8587
opts.SkipSchemaValidation = true
8688
m, err := resolver.Build(ctx, src, opts)
@@ -120,7 +122,8 @@ func run(cmd *cobra.Command, args []string) error {
120122
}
121123

122124
console.Info("")
123-
console.Infof("Running '%s' in Docker with the current directory mounted as a volume...", strings.Join(args, " "))
125+
console.Infof("Running %s in Docker with the current directory mounted as a volume...", console.Bold(strings.Join(args, " ")))
126+
console.Info("")
124127

125128
err = docker.Run(ctx, dockerClient, runOptions)
126129
// Only retry if we're using a GPU but the user didn't explicitly select a GPU with --gpus

pkg/cli/serve.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"fmt"
45
"os"
56
"strings"
67

@@ -81,6 +82,8 @@ func cmdServe(cmd *cobra.Command, arg []string) error {
8182
return err
8283
}
8384

85+
console.Info("Building Docker image from environment in cog.yaml...")
86+
console.Info("")
8487
resolver := model.NewResolver(dockerClient, registry.NewRegistryClient())
8588
m, err := resolver.Build(ctx, src, serveBuildOptions(cmd))
8689
if err != nil {
@@ -130,9 +133,9 @@ func cmdServe(cmd *cobra.Command, arg []string) error {
130133
runOptions.Ports = append(runOptions.Ports, command.Port{HostPort: port, ContainerPort: 5000})
131134

132135
console.Info("")
133-
console.Infof("Running '%[1]s' in Docker with the current directory mounted as a volume...", strings.Join(args, " "))
136+
console.Infof("Running %[1]s in Docker with the current directory mounted as a volume...", console.Bold(strings.Join(args, " ")))
134137
console.Info("")
135-
console.Infof("Serving at http://127.0.0.1:%[1]v", port)
138+
console.Infof("Serving at %s", console.Bold(fmt.Sprintf("http://127.0.0.1:%v", port)))
136139
console.Info("")
137140

138141
err = docker.Run(ctx, dockerClient, runOptions)

pkg/cli/train.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ func cmdTrain(cmd *cobra.Command, args []string) error {
7373
return err
7474
}
7575

76+
console.Info("Building Docker image from environment in cog.yaml...")
77+
console.Info("")
7678
m, err := resolver.Build(ctx, src, serveBuildOptions(cmd))
7779
if err != nil {
7880
return err
@@ -108,7 +110,7 @@ func cmdTrain(cmd *cobra.Command, args []string) error {
108110
}
109111

110112
console.Info("")
111-
console.Infof("Starting Docker image %s...", imageName)
113+
console.Info("Starting Docker image and running setup()...")
112114

113115
predictor, err := predict.NewPredictor(ctx, command.RunOptions{
114116
GPUs: gpus,

pkg/dockerfile/standard_generator.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ func (g *StandardGenerator) installCog() (string, error) {
609609
case wheels.WheelSourceURL:
610610
console.Infof("Using coglet wheel from URL: %s", cogletConfig.URL)
611611
case wheels.WheelSourceFile:
612-
console.Infof("Using local coglet wheel: %s", cogletConfig.Path)
612+
console.Debugf("Using local coglet wheel: %s", cogletConfig.Path)
613613
}
614614

615615
cogletIsPreRelease := sdkIsPreRelease ||
@@ -634,7 +634,7 @@ func (g *StandardGenerator) installCog() (string, error) {
634634
console.Infof("Using cog wheel from URL: %s", wheelConfig.URL)
635635
cogInstall, err = g.installWheelFromURL(wheelConfig.URL)
636636
case wheels.WheelSourceFile:
637-
console.Infof("Using local cog wheel: %s", wheelConfig.Path)
637+
console.Debugf("Using local cog wheel: %s", wheelConfig.Path)
638638
cogInstall, err = g.installWheelFromFile(wheelConfig.Path)
639639
default:
640640
return "", fmt.Errorf("unknown wheel source: %v", wheelConfig.Source)

0 commit comments

Comments
 (0)