Skip to content
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9075ab5
fix: mount weights in cog serve like cog run does (#3044)
markphelps Jun 2, 2026
a1b710a
Bump version to 0.21.0-rc.2 (#3045)
markphelps Jun 2, 2026
9b9f310
chore: regen lockfile
markphelps Jun 2, 2026
8428257
ci: pin mise version in release workflows (#3046)
markphelps Jun 2, 2026
b5c8563
test: cover kong command registration and root globals
markphelps Jun 3, 2026
127e8b1
refactor: share build command logic between cobra and kong
markphelps Jun 3, 2026
957c924
refactor: share push command logic between cobra and kong
markphelps Jun 3, 2026
95b223e
feat: add kong serve and exec parity via shared runtime runners
markphelps Jun 3, 2026
1477b1f
feat: add kong init, login, doctor, debug parity
markphelps Jun 3, 2026
1f1b4c4
feat: add kong predict, run, and train parity via shared runners
markphelps Jun 3, 2026
89a4b4f
feat: add kong weights command parity and fix version short-flag coll…
markphelps Jun 3, 2026
acff164
feat: add kong base-image command parity and remove stubs
markphelps Jun 3, 2026
92f0237
test: verify kong cli parity and clean up unused cobra helpers
markphelps Jun 3, 2026
63c65df
feat: support JSON-native union inputs (#3048)
markphelps Jun 5, 2026
2ab4cc4
test: fuzz schema generation for union inputs + docs fixup (#3049)
markphelps Jun 5, 2026
d6c2b70
Bump version to 0.21.0-rc.3 (#3050)
markphelps Jun 5, 2026
4c0596d
Merge remote-tracking branch 'origin/main' into kong-cli-parity
markphelps Jun 8, 2026
bf8a249
fix: hide kong predict/train/weights/debug and drop dead os.Exit
markphelps Jun 8, 2026
f83efb3
fix: close kong/cobra CLI parity gaps from review
markphelps Jun 8, 2026
89369f1
fix: address Kong/Cobra CLI parity review feedback
markphelps Jun 11, 2026
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
62 changes: 62 additions & 0 deletions cmd/cog-kong/baseimage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"context"

"github.com/replicate/cog/pkg/cli"
"github.com/replicate/cog/pkg/docker/command"
)

// BaseImageCmd implements the experimental "cog base-image" command group.
type BaseImageCmd struct {
Dockerfile BaseImageDockerfileCmd `cmd:"" help:"Display Cog base image Dockerfile."`
Build BaseImageBuildCmd `cmd:"" help:"Build Cog base image."`
}

// baseImageVersionFlags groups the version-selecting flags shared by the
// base-image subcommands.
type baseImageVersionFlags struct {
CUDA string `name:"cuda" help:"CUDA version."`
Python string `name:"python" help:"Python version."`
Torch string `name:"torch" help:"Torch version."`

// Hidden flags for parity with the Cobra base-image command.
BreakSystemPackages bool `name:"break-system-packages" hidden:"" help:"Allow pip to modify uv-managed Python installs."`
BuildContextDir string `name:"build-context-dir" hidden:"" help:"Directory for generated Docker build context artifacts."`
Timestamp int64 `name:"timestamp" hidden:"" default:"-1" help:"Number of seconds since Epoch to use for the build timestamp."`
}

func (f baseImageVersionFlags) options() cli.BaseImageOptions {
return cli.BaseImageOptions{
CUDAVersion: f.CUDA,
PythonVersion: f.Python,
TorchVersion: f.Torch,
BreakSystemPackages: f.BreakSystemPackages,
BuildContextDir: f.BuildContextDir,
Timestamp: f.Timestamp,
}
}

// BaseImageDockerfileCmd implements "cog base-image dockerfile".
type BaseImageDockerfileCmd struct {
baseImageVersionFlags `embed:""`

NoCache bool `name:"no-cache" help:"Do not use cache when building the image."`
Progress string `name:"progress" default:"${progress_default}" enum:"auto,plain,tty,quiet" help:"Set type of build progress output: ${enum}."`
}

func (cmd *BaseImageDockerfileCmd) Run(ctx context.Context) error {
opts := cmd.options()
opts.NoCache = cmd.NoCache
opts.ProgressOutput = cmd.Progress
return cli.RunBaseImageDockerfile(ctx, opts)
Comment thread
markphelps marked this conversation as resolved.
}

// BaseImageBuildCmd implements "cog base-image build".
type BaseImageBuildCmd struct {
baseImageVersionFlags `embed:""`
}

func (cmd *BaseImageBuildCmd) Run(ctx context.Context, dockerClient command.Command) error {
return cli.RunBaseImageBuild(ctx, dockerClient, cmd.options())
}
30 changes: 8 additions & 22 deletions cmd/cog-kong/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ package main
import (
"context"

"github.com/replicate/cog/pkg/config"
"github.com/replicate/cog/pkg/cli"
"github.com/replicate/cog/pkg/docker/command"
"github.com/replicate/cog/pkg/model"
"github.com/replicate/cog/pkg/registry"
"github.com/replicate/cog/pkg/util/console"
)

// BuildCmd implements the "cog build" command.
Expand All @@ -22,23 +20,11 @@ func (cmd *BuildCmd) Validate() error {
return cmd.ValidateMutualExclusivity()
}

// Run executes the build command.
func (cmd *BuildCmd) Run(ctx context.Context, dockerClient command.Command, regClient registry.Client, src *model.Source) error {
imageName := src.Config.Image
if cmd.Tag != "" {
imageName = cmd.Tag
}
if imageName == "" {
imageName = config.DockerImageName(src.ProjectDir)
}

resolver := model.NewResolver(dockerClient, regClient)
m, err := resolver.Build(ctx, src, cmd.BuildOptions(imageName, nil))
if err != nil {
return err
}

console.Infof("\nImage built as %s", m.ImageRef())

return nil
// Run executes the build command via the shared cli.RunBuild runner.
func (cmd *BuildCmd) Run(ctx context.Context, dockerClient command.Command, regClient registry.Client) error {
return cli.RunBuild(ctx, dockerClient, regClient, cli.BuildCommandOptions{
ConfigFilename: cmd.File,
Tag: cmd.Tag,
Flags: cmd.Options(),
})
}
22 changes: 14 additions & 8 deletions cmd/cog-kong/cli.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
package main

import (
"context"
"os"

"github.com/alecthomas/kong"

"github.com/replicate/cog/pkg/global"
"github.com/replicate/cog/pkg/update"
"github.com/replicate/cog/pkg/util/console"
)

// Globals holds flags available to every command.
// The AfterApply hook replaces Cobra's PersistentPreRun.
type Globals struct {
Debug bool `name:"debug" short:"d" env:"COG_DEBUG" help:"Show debugging output."`
NoColor bool `name:"no-color" help:"Disable colored output."`
Help bool `name:"help" short:"h" help:"Show context-sensitive help."`
Registry string `name:"registry" default:"${registry_default}" env:"COG_REGISTRY_HOST" hidden:"" help:"Registry host."`
Profile bool `name:"profile" hidden:"" help:"Enable profiling."`
Version kong.VersionFlag `name:"version" short:"v" help:"Show version of Cog."`
Version kong.VersionFlag `name:"version" help:"Show version of Cog."`
}

// AfterApply runs after flag parsing, before the command's Run.
// This is the Kong equivalent of Cobra's PersistentPreRun.
func (g *Globals) AfterApply(ctx context.Context) error {
func (g *Globals) AfterApply() error {
if g.Debug {
global.Debug = true
console.SetLevel(console.DebugLevel)
}
if g.NoColor {
global.NoColor = true
}
if global.NoColor || !console.ShouldUseColor() {
console.SetColor(false)
}
if global.NoColor {
_ = os.Setenv("NO_COLOR", "1")
}
if g.Profile {
global.ProfilingEnabled = true
}
global.ReplicateRegistryHost = g.Registry

if err := update.DisplayAndCheckForRelease(ctx); err != nil {
console.Debugf("%s", err)
}
return nil
}
27 changes: 16 additions & 11 deletions cmd/cog-kong/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"strings"

"github.com/replicate/cog/pkg/cli"
"github.com/replicate/cog/pkg/config"
"github.com/replicate/cog/pkg/model"
)
Expand Down Expand Up @@ -49,26 +50,30 @@ func (b *BuildFlags) AfterApply() error {
return nil
}

// BuildOptions constructs a model.BuildOptions from the current flag values.
// The imageName and annotations parameters vary by caller (build vs push).
func (b *BuildFlags) BuildOptions(imageName string, annotations map[string]string) model.BuildOptions {
return model.BuildOptions{
ImageName: imageName,
Secrets: b.Secrets,
// Options converts the Kong build flags into the parser-independent
// cli.BuildFlagsOptions shared with the Cobra CLI.
func (b *BuildFlags) Options() cli.BuildFlagsOptions {
return cli.BuildFlagsOptions{
NoCache: b.NoCache,
SeparateWeights: b.SeparateWeights,
UseCudaBaseImage: b.UseCudaBaseImage,
Secrets: b.Secrets,
ProgressOutput: b.Progress,
SchemaFile: b.OpenAPISchema,
DockerfileFile: b.Dockerfile,
UseCudaBaseImage: b.UseCudaBaseImage,
UseCogBaseImage: b.UseCogBaseImage,
OpenAPISchema: b.OpenAPISchema,
DockerfileFile: b.Dockerfile,
Strip: b.Strip,
Precompile: b.Precompile,
Annotations: annotations,
OCIIndex: model.OCIIndexEnabled(),
Timestamp: b.Timestamp,
}
}

// BuildOptions constructs a model.BuildOptions from the current flag values.
// The imageName and annotations parameters vary by caller (build vs push).
func (b *BuildFlags) BuildOptions(imageName string, annotations map[string]string) model.BuildOptions {
return b.Options().ModelBuildOptions(imageName, annotations)
}

// ValidateMutualExclusivity ensures that at most one of --use-cog-base-image,
// --use-cuda-base-image, and --dockerfile is explicitly set.
func (b *BuildFlags) ValidateMutualExclusivity() error {
Expand Down
70 changes: 48 additions & 22 deletions cmd/cog-kong/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/alecthomas/kong"

"github.com/replicate/cog/pkg/global"
"github.com/replicate/cog/pkg/update"
"github.com/replicate/cog/pkg/util/console"
)

Expand All @@ -26,35 +27,27 @@ var (
type CLI struct {
Globals

Build BuildCmd `cmd:"" help:"Build an image from cog.yaml."`
Push PushCmd `cmd:"" help:"Build and push model in current directory to a Docker registry."`
BaseImage BaseImageCmd `cmd:"" name:"base-image" help:"Tools for working with Cog base images."`
Build BuildCmd `cmd:"" help:"Build an image from cog.yaml."`
Debug DebugCmd `cmd:"" help:"Debug Cog internals."`
Doctor DoctorCmd `cmd:"" help:"Check your project for common issues and fix them (experimental)."`
Comment thread
markphelps marked this conversation as resolved.
Exec ExecCmd `cmd:"" help:"Execute a command inside a Docker environment."`
Init InitCmd `cmd:"" help:"Configure your project for use with Cog."`
Comment thread
markphelps marked this conversation as resolved.
Login LoginCmd `cmd:"" help:"Log in to a container registry."`
Predict PredictCmd `cmd:"" help:"Run a prediction."`
Push PushCmd `cmd:"" help:"Build and push model in current directory to a Docker registry."`
RunCommand RunCmd `cmd:"" name:"run" help:"Run a prediction."`
Serve ServeCmd `cmd:"" help:"Run an HTTP server."`
Train TrainCmd `cmd:"" help:"Run a training job."`
Comment thread
markphelps marked this conversation as resolved.
Outdated
Weights WeightsCmd `cmd:"" help:"Commands for managing model weight files."`
Comment thread
markphelps marked this conversation as resolved.
Outdated
}

func main() {
ctx, cancel := newCancellationContext()

var cli CLI

initOpts := []kong.Option{
// CLI metadata and variable interpolation for struct tags
kong.Name("cog"),
kong.Description("Containers for machine learning."),
kong.Vars{
"version": fmt.Sprintf("cog version %s (built %s)", version, buildTime),
"commit": commit,
"progress_default": progressDefault(),
"registry_default": global.DefaultReplicateRegistryHost,
},
kong.UsageOnError(),

// bindings for lazily injecting dependencies into Run() methods
kong.BindTo(ctx, (*context.Context)(nil)),
kong.BindSingletonProvider(provideDockerClient),
kong.BindToProvider(provideRegistryClient),
kong.BindSingletonProvider(provideProviderRegistry),
}

parser, err := kong.New(&cli, initOpts...)
parser, err := newParser(ctx, &cli)
if err != nil {
// Fatal error creating the parser — this is a bug, so panic to get a stack trace.
panic(err)
Expand All @@ -74,14 +67,47 @@ func main() {

// otherwise it's a real parse error (e.g. unexpected command or flag), so print the error and exit non-zero.
parser.FatalIfErrorf(err)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two systematic divergences from Cobra flow through FatalIfErrorf here:

  • Exit code: parse/usage errors exit 80 (Kong's ParseError.ExitCode()); the Cobra CLI exits 1. Anything asserting on exit codes (CI, scripts, integration tests) will see the difference.
  • stderr prefix: Kong prints cog: error: <msg> (from kong.Name("cog") + UsageOnError()); Cobra prints the bare message. Body + exit code otherwise match, e.g.:
kong : cog: error: cog.yaml not found in <dir> ...
main : cog.yaml not found in <dir> ...

Worth a decision on whether parity requires matching Cobra's exit 1 + bare text.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remaining --help short-circuit gap after this commit. Dropping NoDefaultHelp correctly makes enum/mutex/file errors short-circuit to help, but extra positional args still don'tcog <cmd> <extra> --help errors instead of printing help:

cog predict a b --help   # kong → 80 "unexpected argument b";  cobra → 0 (help)
cog login extra --help   # kong → 80 "unexpected argument extra"; cobra → 0 (help)

The rescue at line 63 only matches strings.HasPrefix(parseErr.Error(), "expected"), so unexpected argument … parse errors fall through to FatalIfErrorf and exit 80. This is the only bucket still firing in the flag fuzzer (HELP_VS_ARGS, ~50 unique combos). Likely the same fix should also defer positional-arity validation past Kong's help hook.

os.Exit(1)
Comment thread
markphelps marked this conversation as resolved.
Outdated
}
if cli.Help {
_ = kctx.PrintUsage(false)
return
}

displayUpdateCheck(ctx)
err = kctx.Run()
cancel()
// command returned an error. Print and exit non-zero.
if err != nil {
parser.FatalIfErrorf(err)
os.Exit(1)
Comment thread
markphelps marked this conversation as resolved.
Outdated
}
}

func displayUpdateCheck(ctx context.Context) {
if err := update.DisplayAndCheckForRelease(ctx); err != nil {
console.Debugf("%s", err)
}
}

func newParser(ctx context.Context, cli *CLI, options ...kong.Option) (*kong.Kong, error) {
defaultOptions := []kong.Option{
kong.Name("cog"),
kong.Description("Containers for machine learning."),
kong.Vars{
"version": fmt.Sprintf("cog version %s (built %s)", version, buildTime),
"commit": commit,
"progress_default": progressDefault(),
"registry_default": global.DefaultReplicateRegistryHost,
},
kong.UsageOnError(),
kong.NoDefaultHelp(),

@anish-sahoo anish-sahoo Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--help doesn't short-circuit. With NoDefaultHelp(), help is handled by the cli.Help check after parser.Parse() (L71), but Parse() has already run Validate(), arg-count checks, enum validation and the existingfile mapper — so validation errors before help is ever shown:

cog exec --help                                                  # exit 80 (requires 1 arg)
cog push --use-cog-base-image --use-cuda-base-image=false --help # mutex error
cog build --progress bogus --help                                # enum error
cog build --dockerfile missing --help                            # stat error

Expected (Cobra): all print help and exit 0. Make --help fire before validation — e.g. use Kong's built-in help (a BeforeReset hook that runs before Validate) instead of NoDefaultHelp() + a post-parse bool.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 89369f1. Switched to Kong's built-in --help flag (removed NoDefaultHelp() and the post-parse cli.Help check). Help now fires in a BeforeReset hook before Validate()/arg-count/enum/file mapping, so cog exec --help, the mutex case, --progress bogus --help, and --dockerfile missing --help all print help and exit 0, matching Cobra. (--dockerfile also dropped type:"existingfile" so the file check defers to build time like Cobra.)

kong.BindTo(ctx, (*context.Context)(nil)),
kong.BindSingletonProvider(provideDockerClient),
kong.BindToProvider(provideRegistryClient),
kong.BindSingletonProvider(provideProviderRegistry),
}
return kong.New(cli, append(defaultOptions, options...)...)
}

func newCancellationContext() (context.Context, context.CancelFunc) {
Expand Down
Loading
Loading