Thanks for wanting to help! Here's how to get started.
Each manager is a single Go file in internal/manager/. Look at any existing one (e.g., snap.go or gem.go) for the pattern.
Create internal/manager/yourpkg.go:
package manager
import (
"bufio"
"os/exec"
"strings"
"time"
"github.com/neur0map/glazepkg/internal/model"
)
type YourPkg struct{}
func (y *YourPkg) Name() model.Source { return model.SourceYourPkg }
func (y *YourPkg) Available() bool { return commandExists("yourpkg") }
func (y *YourPkg) Scan() ([]model.Package, error) {
out, err := exec.Command("yourpkg", "list").Output()
if err != nil {
return nil, err
}
var pkgs []model.Package
scanner := bufio.NewScanner(strings.NewReader(string(out)))
for scanner.Scan() {
line := scanner.Text()
// parse name and version from the line
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
pkgs = append(pkgs, model.Package{
Name: fields[0],
Version: fields[1],
Source: model.SourceYourPkg,
InstalledAt: time.Now(),
})
}
return pkgs, nil
}You can also implement any of these. Just omit any that your manager doesn't support — the UI adapts automatically.
// Upgrade a package (press u in detail view)
func (y *YourPkg) UpgradeCmd(name string) *exec.Cmd {
return exec.Command("yourpkg", "upgrade", name)
}
// Remove a package (press x in detail view)
func (y *YourPkg) RemoveCmd(name string) *exec.Cmd {
return exec.Command("yourpkg", "uninstall", name)
}
// Remove with orphaned deps (shown as a second option in the remove modal)
func (y *YourPkg) RemoveCmdWithDeps(name string) *exec.Cmd {
return exec.Command("yourpkg", "uninstall", "--recursive", name)
}
// Search for available packages (press i in list view)
func (y *YourPkg) Search(query string) ([]model.Package, error) {
// parse CLI output into []model.Package with Name, Version, Source, Description
}
// Install a new package (from search results)
func (y *YourPkg) InstallCmd(name string) *exec.Cmd {
return exec.Command("yourpkg", "install", name)
}
// Update detection
func (y *YourPkg) CheckUpdates(pkgs []model.Package) map[string]string {
// return map of name → latest version
}
// Package descriptions
func (y *YourPkg) Describe(pkgs []model.Package) map[string]string {
// return map of name → description
}
// Dependency info (shown when pressing d in detail view)
func (y *YourPkg) ListDependencies(pkgs []model.Package) map[string][]string {
// return map of name → list of dependency names
}If the tool requires root, use privilegedCmd instead of exec.Command for upgrade, remove, and install commands. This wraps with sudo -S on Unix and is a pass-through on Windows.
Four files need a one-line addition each:
internal/model/package.go — add a source constant:
SourceYourPkg Source = "yourpkg"internal/manager/manager.go — add to the All() slice:
&YourPkg{},internal/ui/tabs.go — add to the sources list:
{model.SourceYourPkg, "yourpkg"},internal/ui/theme.go — pick a badge color from the palette (ColorBlue, ColorGreen, ColorRed, ColorCyan, ColorPurple, ColorOrange, ColorYellow):
model.SourceYourPkg: ColorCyan,Create tests/parsing/yourpkg_test.go with mock CLI output so CI can verify your parser without the tool installed:
package parsing
import (
"strings"
"testing"
)
func TestYourPkgListParsing(t *testing.T) {
// Paste real output from `yourpkg list` here
output := `package-a 1.0.0
package-b 2.3.1`
type pkg struct{ name, version string }
var pkgs []pkg
for _, line := range strings.Split(output, "\n") {
fields := strings.Fields(line)
if len(fields) >= 2 {
pkgs = append(pkgs, pkg{fields[0], fields[1]})
}
}
if len(pkgs) != 2 {
t.Fatalf("expected 2 packages, got %d", len(pkgs))
}
if pkgs[0].name != "package-a" || pkgs[0].version != "1.0.0" {
t.Errorf("pkg 0: %+v", pkgs[0])
}
}go build ./cmd/gpk
go vet ./...
go test ./...- Keep PRs focused on one thing
- Add tests for any new parsing logic
- Make sure
go vet ./...passes - If you're adding a package manager you can't test, note that in the PR
If you used Claude, Copilot, ChatGPT, or any other coding agent to help write your code, mention it in the PR description. Just a short note like "used Claude for the initial scaffold" is fine. We want to know what was human-reviewed vs generated.
Do not include Co-Authored-By lines from AI tools in your commits. Keep commit authorship to actual humans.