A Go library that provides a unified interface for interacting with multiple package managers (Homebrew, Flatpak, and Snap). The library abstracts package management operations like search, install, uninstall, update, and upgrade across different backends with consistent error handling and progress reporting.
- Multi-Backend Support: Unified API for Homebrew, Flatpak, and Snap
- Consistent Interface: Same methods work across all supported package managers
- Progress Reporting: Built-in progress reporting with hierarchical action/task/step tracking
- Error Handling: Structured error types with detailed context
- CLI & API Operations: Brew uses REST API for search, all backends use CLI for operations
- Type Safety: Strongly typed package references and operation results
| Backend | Search | Update | Upgrade | Install | Uninstall | List |
|---|---|---|---|---|---|---|
| Homebrew | ✅ API | ✅ CLI | ✅ CLI | ✅ CLI | ✅ CLI | ✅ CLI |
| Flatpak | ✅ CLI | ✅ CLI | ✅ CLI | ✅ CLI | ✅ CLI | ✅ CLI |
| Snap | ✅ CLI | ✅ CLI | ✅ CLI | ✅ CLI | ✅ CLI | ✅ CLI |
go get github.com/frostyard/pmpackage main
import (
"context"
"fmt"
"log"
"github.com/frostyard/pm"
)
func main() {
// Create a Homebrew backend
mgr := pm.NewBrew()
ctx := context.Background()
// Check if the backend is available
available, err := mgr.Available(ctx)
if err != nil {
log.Fatalf("Failed to check availability: %v", err)
}
if !available {
log.Fatal("Homebrew is not available")
}
// Search for packages
packages, err := mgr.Search(ctx, "wget", pm.SearchOptions{})
if err != nil {
log.Fatalf("Search failed: %v", err)
}
fmt.Printf("Found %d packages:\n", len(packages))
for _, pkg := range packages {
fmt.Printf(" - %s\n", pkg.Name)
}
// Install a package
result, err := mgr.Install(ctx, []pm.PackageRef{
{Name: "wget", Kind: "formula"},
}, pm.InstallOptions{})
if err != nil {
log.Fatalf("Install failed: %v", err)
}
if result.Changed {
fmt.Println("Package installed successfully")
}
}package main
import (
"context"
"fmt"
"github.com/frostyard/pm"
)
// Simple progress reporter that prints to stdout
type StdoutReporter struct{}
func (r *StdoutReporter) BeginAction(name string) {
fmt.Printf("==> %s\n", name)
}
func (r *StdoutReporter) EndAction() {
fmt.Println()
}
func (r *StdoutReporter) BeginTask(name string) {
fmt.Printf(" → %s\n", name)
}
func (r *StdoutReporter) EndTask() {}
func (r *StdoutReporter) BeginStep(name string) {
fmt.Printf(" • %s\n", name)
}
func (r *StdoutReporter) EndStep() {}
func (r *StdoutReporter) Info(message string) {
fmt.Printf(" ℹ %s\n", message)
}
func (r *StdoutReporter) Error(message string) {
fmt.Printf(" ✗ %s\n", message)
}
func main() {
// Create backend with progress reporter
mgr := pm.NewFlatpak(pm.WithProgress(&StdoutReporter{}))
ctx := context.Background()
// Update metadata with progress reporting
result, err := mgr.Update(ctx, pm.UpdateOptions{
Progress: &StdoutReporter{},
})
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
if result.Changed {
fmt.Println("Metadata updated")
} else {
fmt.Println("Metadata already up to date")
}
}Check what operations a backend supports:
capabilities, err := mgr.Capabilities(ctx)
if err != nil {
log.Fatalf("Failed to get capabilities: %v", err)
}
for _, cap := range capabilities {
fmt.Printf("%s: %v (%s)\n", cap.Operation, cap.Supported, cap.Notes)
}Manager: Main interface combining all package management operationsSearcher: Search for packagesUpdater: Update package metadata/indicesUpgrader: Upgrade installed packagesInstaller: Install packagesUninstaller: Remove packagesLister: List installed packages
// Homebrew
brew := pm.NewBrew(opts...)
// Flatpak
flatpak := pm.NewFlatpak(opts...)
// Snap
snap := pm.NewSnap(opts...)// Add progress reporting
mgr := pm.NewBrew(pm.WithProgress(reporter))The library provides structured error types:
result, err := mgr.Install(ctx, packages, opts)
if err != nil {
switch {
case pm.IsNotSupported(err):
fmt.Println("Operation not supported")
case pm.IsNotAvailable(err):
fmt.Println("Backend not available")
case pm.IsExternalFailure(err):
// Get detailed error information
extErr := err.(*pm.ExternalFailureError)
fmt.Printf("Command failed:\nStdout: %s\nStderr: %s\n",
extErr.Stdout, extErr.Stderr)
default:
fmt.Printf("Error: %v\n", err)
}
}The repository includes three CLI test harnesses demonstrating library usage:
- brewtest: Homebrew operations demo
- flatpaktest: Flatpak operations demo
- snaptest: Snap operations demo
Build them with:
make build-cliRun them:
./bin/brewtest search wget
./bin/flatpaktest list
./bin/snaptest capabilitiesSee individual README files in cmd/*/README.md for detailed usage.
- Go 1.22 or later
- Make
- One or more of: Homebrew, Flatpak, or Snap (for testing)
# Install development tools
make tools
# Run tests
make test
# Run linter
make lint
# Format code
make fmt
# Run all checks (format, lint, test)
make check# Unit tests
make test
# Full CI suite
make ciThe library is organized as:
pmpackage: Public API withManagerinterface and error typesinternal/types: Shared internal types for operations and resultsinternal/backend/*: Backend implementations (brew, flatpak, snap)internal/runner: Command execution wrapper with structured error handlingcmd/*: Example CLI tools demonstrating library usage
Each backend implements the same set of interfaces:
- Available check: Verify the package manager is installed and accessible
- Capabilities: Report which operations are supported
- Operations: Search, update, upgrade, install, uninstall, list
Progress reporting follows a three-level hierarchy:
- Action: Top-level operation (e.g., "Install")
- Task: Major steps within an action (e.g., "Downloading packages")
- Step: Fine-grained progress updates (e.g., "Fetching package metadata")
MIT License - see LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
Built for the Frostyard project to provide unified package management across different Linux package managers and Homebrew.