feat: registry-agnostic provider system for multi-registry support (#2668)
* feat(provider): add Provider interface definition
* feat(provider): add GenericProvider for OCI registries
* feat(provider): add ReplicateProvider for r8.im registry
Implements the Provider interface for Replicate's r8.im registry:
- MatchesRegistry() checks for r8.im and global registry host
- Login() implements the browser-based token flow (extracted from cli/login.go)
- LoginWithOptions() provides configurable token-stdin support
- PrePush/PostPush are no-ops for now (analytics still in push.go)
This extracts the Replicate-specific login logic into the provider
abstraction, making it reusable and testable independently.
* feat(provider): add Registry for provider lookup and setup
Adds provider registry functionality:
- Registry type with ForImage() and ForHost() methods for provider lookup
- ExtractHost() to parse registry host from image names (handles ports, tags, digests)
- DefaultRegistry() singleton for global access
- setup.Init() to initialize default registry with Replicate and Generic providers
Provider order: Replicate (specific) -> Generic (fallback for any OCI registry)
* refactor(cli): use provider system for cog login
Refactors the login command to use the provider abstraction:
- Initializes provider registry with setup.Init()
- Looks up provider by registry host
- Replicate provider handles its own token-based auth flow
- Generic provider returns ErrUseDockerLogin with helpful message
The Replicate-specific login logic (token verification, browser flow)
now lives in pkg/provider/replicate/replicate.go, making login.go
a thin dispatcher that delegates to the appropriate provider.
Also exports LoginToRegistry() for programmatic use by other commands.
* refactor(cli): integrate provider system into cog push
Updates push command to use the provider abstraction:
- Initializes provider registry with setup.Init()
- Detects target registry via provider.ForImage()
- Uses isReplicate flag for provider-specific behavior
- Local image push (--local-image) now explicitly Replicate-only with clear error
- 404 errors now show registry-appropriate messages
- Replicate model URL only shown when pushing to Replicate
This makes the push command work with any OCI registry while maintaining
Replicate-specific features and helpful error messages.
* refactor(cli): use generic examples in push/pull commands
Updates command examples and error messages to use generic registry
placeholders instead of hardcoded r8.im references:
- 'cog push registry.example.com/your-username/model-name'
- 'cog pull registry.example.com/your-username/model-name'
This makes the documentation more neutral while still being clear about
how to use the commands with any OCI registry.
* feat(cli): warn and disable fast push for non-Replicate registries
When --x-fast flag is used with a non-Replicate registry, instead of
failing or silently ignoring:
- Shows a warning that fast push is Replicate-only
- Automatically falls back to standard push
This maintains backward compatibility while clearly communicating
the feature limitation to users.
* refactor(cli): make coglog analytics conditional for Replicate
Analytics (coglog) calls are now only made when pushing to Replicate:
- Moved config/image detection before Docker client creation
- Determine isReplicate flag before initializing analytics
- logClient.StartPush() and EndPush() only called for Replicate
- Simplified error handling with endPushWithError helper
This ensures non-Replicate push operations don't attempt to contact
Replicate's analytics endpoints, improving privacy and performance
when using other registries.
* docs: update CLI documentation for multi-registry support
- Add documentation for pushing to non-Replicate registries (GHCR, GCR, etc.)
- Clarify that 'cog login' is for Replicate; use 'docker login' for others
- Add examples showing multi-registry workflows
* refactor: use PrePush/PostPush hooks instead of isReplicate checks
Major refactoring to move registry-specific logic into provider hooks:
Provider interface changes:
- Add PushOptions struct to consolidate all push parameters
- PrePush(ctx, opts) validates options and starts analytics
- PostPush(ctx, opts, pushErr) ends analytics and shows messages
GenericProvider:
- PrePush() errors on --local-image (not supported)
- PrePush() warns on --x-fast (not supported, falls back)
- PostPush() shows simple success message
ReplicateProvider:
- PrePush() starts coglog analytics, shows 'Fast push enabled'
- PostPush() ends analytics, shows Replicate URL on success
- PostPush() returns Replicate-specific error messages for 404s
push.go is now much cleaner:
- No more scattered isReplicate checks (reduced from 6+ to 1)
- Single PrePush/PostPush call handles all provider-specific behavior
- Generic flow: setup -> PrePush -> build -> push -> PostPush
The remaining p.Name() check is for disabling FastPush after warning,
which could be improved with a SupportsFastPush() method in the future.
* feat(provider): implement interactive login for generic registries
GenericProvider now handles login directly by prompting for username
and password, then saving credentials via Docker's credential system.
Changes:
- GenericProvider.Login() prompts interactively instead of returning ErrUseDockerLogin
- Remove ErrUseDockerLogin since it's no longer needed
- Update login.go to remove ErrUseDockerLogin handling
- Update help text to reflect that 'cog login' works for all registries
* docs: document --registry flag for multi-registry login support
- Add --registry to Global Options in cli.md and llms.txt
- Update cog login examples to show --registry usage for non-Replicate registries
- Clarify that --token-stdin is Replicate-specific
* refactor: use typed errors for push repository not found
Replace string-based '404' error checking with typed NotFoundError:
- Add isRepositoryNotFoundError() to detect NAME_UNKNOWN registry errors
- Convert NAME_UNKNOWN to NotFoundError{Object: "repository"} in Push()
- Update ReplicateProvider.PostPush() to use command.IsNotFoundError()
- Remove string wrapper from StandardPush() since error is now typed
- Add tests for NotFoundError handling in PostPush
* chore: cleanup
Signed-off-by: Mark Phelps <mphelps@cloudflare.com>
* chore: debug
Signed-off-by: Mark Phelps <mphelps@cloudflare.com>
* chore: fix build
Signed-off-by: Mark Phelps <mphelps@cloudflare.com>
* chore: fix lint error
Signed-off-by: Mark Phelps <mphelps@cloudflare.com>
* chore: fix generic login/host
Signed-off-by: Mark Phelps <mphelps@cloudflare.com>
* chore: fix build
Signed-off-by: Mark Phelps <mphelps@cloudflare.com>
* test: add integration tests for login command
Add comprehensive integration tests for the login command changes:
- TestLoginGenericRegistryPTY: Tests interactive login flow for generic
registries with PTY-based username/password prompts
- TestLoginProviderRouting: Verifies correct provider selection based on
--registry flag (Replicate for r8.im, generic for others)
- TestLoginEnvironmentVariable: Tests COG_REGISTRY_HOST env var and
--registry flag override behavior
- TestLoginHelp: Verifies help text mentions both Replicate and generic
registry support
- TestLoginSuggestFor: Verifies 'cog auth' suggests 'cog login'
The tests use PTY interaction for generic provider login since it
requires interactive username/password input. Replicate-specific token
verification is tested in unit tests (pkg/provider/replicate/).
* refactor: use raw string literal for multi-line error message
---------
Signed-off-by: Mark Phelps <mphelps@cloudflare.com>
Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>