Contributing to VEET
First off, thank you for considering contributing to VEET!
VEET is an open-source, community-driven tool dedicated to making software uninstallation and residual cleanup on Linux completely thorough, fast, and transparent.
Whether you want to add support for a new package manager, improve the TUI design, optimize detectors, or fix bugs, your help is welcome!
π οΈ Development Setup
Prerequisites
- Go 1.22+ installed (golang.org)
- Linux operating system (or Linux container/WSL)
gitandmake(optional)
Getting Started
Fork and clone the repository:
git clone https://github.com/swadhinbiswas/veet.git cd veetRun tests:
go test -v ./...Build the single binary:
go build -o veet . # or make buildRun the interactive TUI locally:
./veet # or make run
ποΈ Codebase Architecture
veet/
βββ main.go # Single entrypoint & Cobra CLI commands (scan, clean, tui)
βββ internal/
β βββ model/ # Core data types: AppInfo, RemovableFiles, Config, SourceMeta
β βββ detector/ # Parallel detection engine (1 file per package manager/source)
β βββ uninstaller/ # Staging engine, sudo batching & filesystem cleanup (Afero)
β βββ history/ # Append-only audit logger (~/.local/share/veet/history.log)
β βββ ui/ # Bubble Tea & Lip Gloss TUI (table, details, stat cards, styles)
βββ Makefile # Helper build & test targets
βββ CONTRIBUTING.md # Contribution guidelines
π How to Add a New Package Manager or Source
Adding support for a new package manager or tool (e.g. nix, guix, brew, gem, asdf, etc.) is straightforward and modular.
Step 1: Implement the Detector Interface
Create a new file under internal/detector/<your-source>.go:
package detector
import (
"context"
"veet/internal/model"
)
type myToolDetector struct {
ex Execer
}
func NewMyToolDetector(ex Execer) Detector {
return &myToolDetector{ex: ex}
}
// Source returns the unique identifier string
func (d *myToolDetector) Source() string {
return "mytool"
}
// Available checks if the CLI tool exists on the system
func (d *myToolDetector) Available() bool {
_, err := d.ex.LookPath("mytool")
return err == nil
}
// Detect scans installed packages and returns them
func (d *myToolDetector) Detect(ctx context.Context) ([]model.AppInfo, error) {
out, err := Exec(ctx, d.ex, "mytool", "list")
if err != nil {
return nil, err
}
var apps []model.AppInfo
// Parse output lines into model.AppInfo structs...
return apps, nil
}
Step 2: Register in internal/detector/registry.go
Add your detector to Registry():
func Registry(ex Execer, home string) []Detector {
return []Detector{
// ...
NewMyToolDetector(ex),
// ...
}
}
Step 3: Add Display Metadata in internal/model/app.go
Define your source's badge label, icon, and brand color in SourceMetaByID:
var SourceMetaByID = map[string]SourceMeta{
// ...
"mytool": {Label: "MyTool", Icon: "βοΈ", Color: "#00E5FF"},
}
Step 4: Add Uninstall Command in internal/uninstaller/uninstaller.go
Define how the package manager removes the application in RemoveCommand():
switch app.Source {
// ...
case "mytool":
return []string{"mytool", "uninstall", "-y", app.Name}
}
π§ͺ Testing Guidelines
We prioritize high test coverage and rock-solid safety to ensure no user files are deleted accidentally.
- Run all unit tests:
go test -v ./... - File deletion runs through afero, allowing mock testing against
afero.NewMemMapFs()without touching your real disk. - Command execution is abstracted through the
Execerinterface for mock testing.
π Pull Request Checklist
Before submitting your pull request:
- All tests pass (
go test ./...). - Code is formatted (
go fmt ./...). - Added unit tests for new features or bug fixes.
- Kept commit messages clear and descriptive.