Function bodies 96 total
checkXcodeCLI function · go · L104-L115 (12 LOC)internal/preflight/preflight.go
func checkXcodeCLI() Check {
cmd := exec.Command("xcode-select", "-p")
output, err := cmd.Output()
if err != nil {
return Check{
Name: "Xcode CLI tools",
Passed: false,
Detail: "Not installed. Run: xcode-select --install",
}
}
return Check{Name: "Xcode CLI tools", Passed: true, Detail: strings.TrimSpace(string(output)) + " (ensure up to date via Software Update)"}
}checkAeroSpaceVersion function · go · L117-L155 (39 LOC)internal/preflight/preflight.go
func checkAeroSpaceVersion() Check {
_, found := shell.Which("aerospace")
if !found {
// Not installed yet — that's fine, Homebrew will install it.
return Check{Name: "AeroSpace version", Passed: true, Detail: "Not yet installed (will be installed)"}
}
result, err := shell.Run("aerospace", "--version")
if err != nil {
return Check{
Name: "AeroSpace version",
Passed: false,
Warning: false,
Detail: "Could not determine version. Ensure AeroSpace >= " + MinAeroSpaceVersion,
}
}
// Output format: "aerospace CLI client version: 0.20.3-Beta <hash>\n..."
version := parseAeroSpaceVersion(result.Stdout)
if version == "" {
return Check{
Name: "AeroSpace version",
Passed: false,
Warning: true,
Detail: "Could not parse version. Ensure AeroSpace >= " + MinAeroSpaceVersion,
}
}
if !versionAtLeast(version, MinAeroSpaceVersion) {
return Check{
Name: "AeroSpace version",
Passed: false,
Warning: false,
Detail: fmt.Sprintf("%s parseAeroSpaceVersion function · go · L158-L173 (16 LOC)internal/preflight/preflight.go
func parseAeroSpaceVersion(output string) string {
// First line: "aerospace CLI client version: 0.20.3-Beta <hash>"
for _, line := range strings.Split(output, "\n") {
if strings.Contains(line, "CLI client version:") {
parts := strings.SplitN(line, ":", 2)
if len(parts) < 2 {
continue
}
fields := strings.Fields(strings.TrimSpace(parts[1]))
if len(fields) >= 1 {
return fields[0]
}
}
}
return ""
}versionAtLeast function · go · L177-L213 (37 LOC)internal/preflight/preflight.go
func versionAtLeast(version, minimum string) bool {
parse := func(v string) []int {
// Strip leading 'v' and any pre-release suffix after '-'
v = strings.TrimPrefix(v, "v")
if idx := strings.Index(v, "-"); idx != -1 {
v = v[:idx]
}
var nums []int
for _, seg := range strings.Split(v, ".") {
n, err := strconv.Atoi(seg)
if err != nil {
return nil
}
nums = append(nums, n)
}
return nums
}
ver := parse(version)
min := parse(minimum)
if ver == nil || min == nil {
return false
}
for i := 0; i < len(min); i++ {
if i >= len(ver) {
return false
}
if ver[i] > min[i] {
return true
}
if ver[i] < min[i] {
return false
}
}
return true
}checkSeparateSpaces function · go · L215-L233 (19 LOC)internal/preflight/preflight.go
func checkSeparateSpaces() Check {
result, err := shell.Run("defaults", "read", "com.apple.spaces", "spans-displays")
if err != nil {
// This key may not exist, which is fine (default is separate spaces)
return Check{Name: "Separate Spaces", Passed: true, Detail: "Enabled (default)", Warning: false}
}
val := strings.TrimSpace(result.Stdout)
if val == "1" {
return Check{
Name: "Separate Spaces",
Passed: false,
Warning: true,
Detail: "\"Displays have separate Spaces\" may be disabled. Enable in System Settings → Desktop & Dock.",
}
}
return Check{Name: "Separate Spaces", Passed: true, Detail: "Enabled"}
}Run function · go · L19-L38 (20 LOC)internal/shell/exec.go
func Run(name string, args ...string) (Result, error) {
cmd := exec.Command(name, args...)
stdout, err := cmd.Output()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
return Result{
Stdout: string(stdout),
Stderr: string(exitErr.Stderr),
ExitCode: exitErr.ExitCode(),
}, err
}
return Result{}, err
}
return Result{
Stdout: string(stdout),
ExitCode: 0,
}, nil
}RunStreaming function · go · L41-L75 (35 LOC)internal/shell/exec.go
func RunStreaming(name string, args []string, onLine func(string)) error {
cmd := exec.Command(name, args...)
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("stdout pipe: %w", err)
}
stderrPipe, err := cmd.StderrPipe()
if err != nil {
return fmt.Errorf("stderr pipe: %w", err)
}
if err := cmd.Start(); err != nil {
return fmt.Errorf("start: %w", err)
}
var wg sync.WaitGroup
scanLines := func(r io.Reader) {
defer wg.Done()
scanner := bufio.NewScanner(r)
for scanner.Scan() {
onLine(scanner.Text())
}
}
wg.Add(2)
go scanLines(stdoutPipe)
go scanLines(stderrPipe)
wg.Wait()
if err := cmd.Wait(); err != nil {
return err
}
return nil
}Repobility · MCP-ready · https://repobility.com
Which function · go · L78-L84 (7 LOC)internal/shell/exec.go
func Which(name string) (string, bool) {
path, err := exec.LookPath(name)
if err != nil {
return "", false
}
return path, true
}NewApp function · go · L36-L50 (15 LOC)internal/tui/app.go
func NewApp(phaseNames []string, installer InstallerFunc, splashOpts SplashOptions, version string) App {
title := "Omachy Installer"
if splashOpts.Uninstall {
title = "Omachy Uninstaller"
}
return App{
header: NewHeaderModel(title, 80),
phases: NewPhasesModel(phaseNames),
output: NewOutputModel(56, 15),
help: NewHelpModel(80),
installer: installer,
splashOpts: splashOpts,
version: version,
}
}Init method · go · L52-L54 (3 LOC)internal/tui/app.go
func (a App) Init() tea.Cmd {
return a.phases.spinner.Tick
}Update method · go · L56-L160 (105 LOC)internal/tui/app.go
func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.WindowSizeMsg:
a.width = msg.Width
a.height = msg.Height
a.layout()
// Don't return early — let the viewport also process the resize
case tea.KeyMsg:
// Confirmation dialog intercepts all keys
if a.showConfirm {
switch msg.String() {
case "y", "Y":
a.logoutRequested = true
return a, tea.Quit
case "n", "N", "esc", "q":
a.showConfirm = false
return a, tea.Quit
}
return a, nil
}
switch msg.String() {
case "q", "ctrl+c":
return a, tea.Quit
case "enter":
if a.waitingForUser {
a.waitingForUser = false
close(a.waitDone)
return a, nil
}
if !a.started {
a.started = true
// Launch the installer goroutine now
return a, func() tea.Msg {
go a.installer(a.program)
return nil
}
}
if a.finished {
if a.err == nil && !a.splashOpts.DryRun {
a.showConfirm = true
return aView method · go · L162-L212 (51 LOC)internal/tui/app.go
func (a App) View() string {
if a.width == 0 {
return "Loading..."
}
// Splash screen before installation starts
if !a.started {
return renderSplash(a.width, a.height, a.splashOpts, a.version)
}
header := a.header.View()
// Phase panel
contentHeight := a.height - 4 // header + help + borders
if contentHeight < 3 {
contentHeight = 3
}
phaseContent := a.phases.View(contentHeight - 2) // panel border
phasePanel := panelStyle.
Width(phasePanelWidth).
Height(contentHeight).
Render(panelTitleStyle.Render("Phases") + "\n" + phaseContent)
// Output panel
outputWidth := a.width - phasePanelWidth - 5
if outputWidth < 10 {
outputWidth = 10
}
outputPanel := panelStyle.
Width(outputWidth).
Height(contentHeight).
Render(panelTitleStyle.Render("Output") + "\n" + a.output.View())
body := lipgloss.JoinHorizontal(lipgloss.Top, phasePanel, outputPanel)
help := a.help.View()
base := lipgloss.JoinVertical(lipgloss.Left, header, body, help)
if a.showConfirm {layout method · go · L214-L231 (18 LOC)internal/tui/app.go
func (a *App) layout() {
a.header.Width = a.width
a.help.Width = a.width
contentHeight := a.height - 4
if contentHeight < 3 {
contentHeight = 3
}
outputWidth := a.width - phasePanelWidth - 7
if outputWidth < 10 {
outputWidth = 10
}
outputHeight := contentHeight - 3
if outputHeight < 1 {
outputHeight = 1
}
a.output.SetSize(outputWidth, outputHeight)
}Run function · go · L241-L258 (18 LOC)internal/tui/app.go
func Run(phaseNames []string, installer InstallerFunc, splashOpts SplashOptions, version string) (RunResult, error) {
app := NewApp(phaseNames, installer, splashOpts, version)
p := tea.NewProgram(&app, tea.WithAltScreen())
app.program = p
model, err := p.Run()
if err != nil {
return RunResult{}, err
}
switch a := model.(type) {
case *App:
return RunResult{Finished: a.finished, Err: a.err, LogoutRequested: a.logoutRequested}, nil
case App:
return RunResult{Finished: a.finished, Err: a.err, LogoutRequested: a.logoutRequested}, nil
}
return RunResult{}, nil
}renderConfirmDialog function · go · L27-L35 (9 LOC)internal/tui/confirm.go
func renderConfirmDialog(title, message string) string {
content := fmt.Sprintf(
"%s\n\n%s\n\n%s",
confirmTitleStyle.Render(title),
confirmTextStyle.Render(message),
confirmHintStyle.Render("[y] yes [n] no"),
)
return confirmBoxStyle.Render(content)
}About: code-quality intelligence by Repobility · https://repobility.com
NewHeaderModel function · go · L16-L21 (6 LOC)internal/tui/header.go
func NewHeaderModel(title string, width int) HeaderModel {
return HeaderModel{
Title: title,
Width: width,
}
}View method · go · L23-L37 (15 LOC)internal/tui/header.go
func (m HeaderModel) View() string {
left := titleStyle.Render(m.Title)
right := progressStyle.Render(fmt.Sprintf("%d%%", m.Percent))
gap := m.Width - lipgloss.Width(left) - lipgloss.Width(right) - 2
if gap < 1 {
gap = 1
}
line := lipgloss.NewStyle().
Foreground(colorMuted).
Render(repeatChar('─', gap))
return fmt.Sprintf(" %s %s %s ", left, line, right)
}repeatChar function · go · L39-L48 (10 LOC)internal/tui/header.go
func repeatChar(c rune, n int) string {
if n < 0 {
n = 0
}
b := make([]rune, n)
for i := range b {
b[i] = c
}
return string(b)
}NewHelpModel function · go · L11-L13 (3 LOC)internal/tui/help.go
func NewHelpModel(width int) HelpModel {
return HelpModel{Width: width}
}View method · go · L15-L39 (25 LOC)internal/tui/help.go
func (m HelpModel) View() string {
bindings := []struct{ key, desc string }{
{"q", "quit"},
{"↑↓", "scroll"},
}
if m.Finished {
bindings = []struct{ key, desc string }{
{"q", "quit"},
{"enter", "exit"},
}
}
var s string
for i, b := range bindings {
if i > 0 {
s += " "
}
s += fmt.Sprintf("%s %s",
helpKeyStyle.Render("["+b.key+"]"),
helpStyle.Render(b.desc),
)
}
return " " + s
}NewOutputModel function · go · L15-L22 (8 LOC)internal/tui/output.go
func NewOutputModel(width, height int) OutputModel {
vp := viewport.New(width, height)
vp.SetContent("")
return OutputModel{
viewport: vp,
autoScroll: true,
}
}Update method · go · L24-L36 (13 LOC)internal/tui/output.go
func (m OutputModel) Update(msg tea.Msg) (OutputModel, tea.Cmd) {
var cmd tea.Cmd
m.viewport, cmd = m.viewport.Update(msg)
// If user scrolled up, disable auto-scroll
if m.viewport.AtBottom() {
m.autoScroll = true
} else {
m.autoScroll = false
}
return m, cmd
}AppendLine method · go · L38-L51 (14 LOC)internal/tui/output.go
func (m *OutputModel) AppendLine(text string) {
m.lines = append(m.lines, text)
content := ""
for i, line := range m.lines {
if i > 0 {
content += "\n"
}
content += line
}
m.viewport.SetContent(content)
if m.autoScroll {
m.viewport.GotoBottom()
}
}Powered by Repobility — scan your code at https://repobility.com
View method · go · L53-L55 (3 LOC)internal/tui/output.go
func (m OutputModel) View() string {
return m.viewport.View()
}SetSize method · go · L57-L60 (4 LOC)internal/tui/output.go
func (m *OutputModel) SetSize(width, height int) {
m.viewport.Width = width
m.viewport.Height = height
}NewPhasesModel function · go · L34-L48 (15 LOC)internal/tui/phases.go
func NewPhasesModel(names []string) PhasesModel {
phases := make([]Phase, len(names))
for i, n := range names {
phases[i] = Phase{Name: n, Status: StatusPending}
}
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(colorPrimary)
return PhasesModel{
Phases: phases,
spinner: s,
}
}Update method · go · L50-L54 (5 LOC)internal/tui/phases.go
func (m PhasesModel) Update(msg tea.Msg) (PhasesModel, tea.Cmd) {
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
return m, cmd
}View method · go · L56-L85 (30 LOC)internal/tui/phases.go
func (m PhasesModel) View(height int) string {
var b strings.Builder
for _, p := range m.Phases {
var icon, name string
switch p.Status {
case StatusPending:
icon = phaseIconPending
name = phaseNamePending.Render(p.Name)
case StatusActive:
icon = m.spinner.View()
name = phaseNameActive.Render(p.Name)
case StatusDone:
icon = phaseIconDone
name = phaseNameDone.Render(p.Name)
case StatusFailed:
icon = phaseIconFailed
name = phaseNameFailed.Render(p.Name)
}
fmt.Fprintf(&b, " %s %s\n", icon, name)
}
// Pad to fill height
lines := len(m.Phases)
for i := lines; i < height; i++ {
b.WriteString("\n")
}
return b.String()
}SetStatus method · go · L87-L94 (8 LOC)internal/tui/phases.go
func (m *PhasesModel) SetStatus(name string, status PhaseStatus) {
for i := range m.Phases {
if m.Phases[i].Name == name {
m.Phases[i].Status = status
return
}
}
}RunQuiet function · go · L13-L33 (21 LOC)internal/tui/quiet.go
func RunQuiet(installer InstallerFunc) (RunResult, error) {
app := &quietApp{}
// Provide a dummy input and discard output so Bubbletea never tries
// to open /dev/tty — this allows running over SSH without a PTY.
p := tea.NewProgram(app,
tea.WithInput(strings.NewReader("")),
tea.WithOutput(io.Discard),
)
app.program = p
go func() {
installer(p)
}()
if _, err := p.Run(); err != nil {
return RunResult{}, err
}
return RunResult{Finished: true, Err: app.lastErr}, nil
}Update method · go · L44-L70 (27 LOC)internal/tui/quiet.go
func (q *quietApp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case PhaseStarted:
fmt.Printf("[%s] starting...\n", msg.Name)
case PhaseCompleted:
fmt.Printf("[%s] done\n", msg.Name)
case PhaseFailed:
fmt.Printf("[%s] FAILED: %v\n", msg.Name, msg.Error)
case LogLine:
fmt.Println(msg.Text)
case WaitForUser:
fmt.Println(msg.Prompt)
fmt.Println(" (quiet mode — skipping wait)")
close(msg.Done)
case InstallFinished:
if msg.Err != nil {
fmt.Printf("\nFailed: %v\n", msg.Err)
} else {
fmt.Println("\nComplete!")
}
q.lastErr = msg.Err
return q, tea.Quit
case ProgressUpdate:
// skip in quiet mode
}
return q, nil
}Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
renderSplash function · go · L45-L75 (31 LOC)internal/tui/splash.go
func renderSplash(width, height int, opts SplashOptions, version string) string {
var b strings.Builder
// Logo
logo := `
___ _
/ _ \ _ __ ___ __ _ ___| |__ _ _
| | | | '_ ` + "`" + ` _ \ / _` + "`" + ` |/ __| '_ \| | | |
| |_| | | | | | | (_| | (__| | | | |_| |
\___/|_| |_| |_|\__,_|\___|_| |_|\__, |
|___/`
b.WriteString(splashLogo.Render(logo))
b.WriteString("\n\n")
b.WriteString(splashSubtitle.Render(" Tiling window manager setup for macOS"))
b.WriteString("\n")
b.WriteString(splashItem.Render(fmt.Sprintf(" Version %s", version)))
b.WriteString("\n\n")
bullet := lipgloss.NewStyle().Foreground(colorPrimary).Render("*")
if opts.Uninstall {
renderUninstallSplash(&b, opts, bullet)
} else {
renderInstallSplash(&b, opts, bullet)
}
b.WriteString("\n")
return b.String()
}renderInstallSplash function · go · L77-L127 (51 LOC)internal/tui/splash.go
func renderInstallSplash(b *strings.Builder, opts SplashOptions, bullet string) {
b.WriteString(splashSection.Render(" This will install and configure:"))
b.WriteString("\n")
tools := []struct{ name, desc string }{
{"AeroSpace", "tiling window manager"},
{"SketchyBar", "custom menu bar"},
{"JankyBorders", "window border highlights"},
{"Ghostty", "terminal emulator"},
{"Neovim + Kickstart", "text editor"},
{"Tmux + TPM", "terminal multiplexer"},
{"Starship", "cross-shell prompt"},
{"fzf", "fuzzy finder"},
{"Lazygit", "git TUI"},
{"Lazydocker", "docker TUI"},
{"Atuin", "shell history search"},
{"Nerd Fonts", "Hack + JetBrains Mono"},
{"Node, Python, Go", "language runtimes"},
}
for _, t := range tools {
b.WriteString(fmt.Sprintf(" %s %s %s\n",
bullet,
lipgloss.NewStyle().Foreground(colorText).Render(t.name),
splashItem.Render(t.desc),
))
}
b.WriteString("\n")
b.WriteString(splashSection.Render(" Additionally:"))
b.WriteString("\n")
renderUninstallSplash function · go · L129-L164 (36 LOC)internal/tui/splash.go
func renderUninstallSplash(b *strings.Builder, opts SplashOptions, bullet string) {
b.WriteString(splashSection.Render(" This will remove Omachy and restore your system:"))
b.WriteString("\n\n")
steps := []string{
"Stop running brew services (SketchyBar, JankyBorders)",
"Remove deployed config files",
"Uninstall packages that Omachy installed",
"Restore original macOS system defaults",
"Restore config backups (if available)",
}
for _, s := range steps {
b.WriteString(fmt.Sprintf(" %s %s\n", bullet, lipgloss.NewStyle().Foreground(colorText).Render(s)))
}
b.WriteString("\n")
b.WriteString(splashSection.Render(" Your system will be returned to its pre-Omachy state."))
b.WriteString("\n")
b.WriteString(fmt.Sprintf(" %s Packages you had before Omachy will not be removed\n", bullet))
b.WriteString(fmt.Sprintf(" %s Original settings will be restored from saved state\n", bullet))
var flags []string
if opts.DryRun {
flags = append(flags, "--dry-run (no chrenderFlags function · go · L166-L175 (10 LOC)internal/tui/splash.go
func renderFlags(b *strings.Builder, flags []string) {
if len(flags) > 0 {
b.WriteString("\n")
b.WriteString(splashSection.Render(" Active flags:"))
b.WriteString("\n")
for _, f := range flags {
b.WriteString(fmt.Sprintf(" %s\n", splashFlag.Render(f)))
}
}
}PhaseNames function · go · L17-L24 (8 LOC)internal/uninstaller/uninstaller.go
func PhaseNames() []string {
return []string{
"Services",
"Configs",
"Packages",
"Defaults",
}
}Run function · go · L34-L73 (40 LOC)internal/uninstaller/uninstaller.go
func Run(p *tea.Program, opts Options) {
// Check if Omachy is actually installed
state, err := installer.LoadState()
if err == nil && len(state.InstalledPackages) == 0 &&
len(state.DeployedConfigs) == 0 &&
len(state.OriginalDefaults) == 0 &&
len(state.Services) == 0 &&
state.BackupPath == "" {
p.Send(tui.LogLine{Text: "Omachy is not installed — nothing to uninstall."})
p.Send(tui.InstallFinished{})
return
}
phases := []struct {
name string
fn func(p *tea.Program, opts Options) error
}{
{"Services", stopServices},
{"Configs", removeConfigs},
{"Packages", removePackages},
{"Defaults", restoreDefaults},
}
for i, phase := range phases {
p.Send(tui.PhaseStarted{Name: phase.name})
err := phase.fn(p, opts)
if err != nil {
p.Send(tui.PhaseFailed{Name: phase.name, Error: err})
p.Send(tui.InstallFinished{Err: fmt.Errorf("phase %q failed: %w", phase.name, err)})
return
}
p.Send(tui.PhaseCompleted{Name: phase.name})
pct := ((i + 1) * 10stopServices function · go · L75-L116 (42 LOC)internal/uninstaller/uninstaller.go
func stopServices(p *tea.Program, opts Options) error {
log := func(text string) { p.Send(tui.LogLine{Text: text}) }
state, err := installer.LoadState()
if err != nil {
return err
}
for _, svc := range state.Services {
if opts.DryRun {
log(fmt.Sprintf("==> Would stop service: %s", svc))
continue
}
if err := brew.StopService(svc, log); err != nil {
log(fmt.Sprintf(" Warning: %v", err))
}
}
// Kill processes that Omachy started directly (not managed as brew services).
// AeroSpace launches sketchybar and borders via after-startup-command.
// Only kill processes that were not already running before install.
preExisting := make(map[string]bool, len(state.RunningProcesses))
for _, p := range state.RunningProcesses {
preExisting[p] = true
}
for _, proc := range []string{"AeroSpace", "sketchybar", "borders"} {
if preExisting[proc] {
log(fmt.Sprintf(" Skipping %s (was running before install)", proc))
continue
}
if opts.DryRun {
log(fmt.removeConfigs function · go · L118-L173 (56 LOC)internal/uninstaller/uninstaller.go
func removeConfigs(p *tea.Program, opts Options) error {
log := func(text string) { p.Send(tui.LogLine{Text: text}) }
if opts.KeepConfigs {
log("==> Keeping configs (--keep-configs)")
return nil
}
state, err := installer.LoadState()
if err != nil {
return err
}
for dest := range state.DeployedConfigs {
if opts.DryRun {
log(fmt.Sprintf("==> Would remove %s", shortPath(dest)))
continue
}
log(fmt.Sprintf("==> Removing %s", shortPath(dest)))
os.RemoveAll(dest)
}
// Restore from backup if available
if state.BackupPath != "" {
if _, err := os.Stat(state.BackupPath); os.IsNotExist(err) {
log(fmt.Sprintf("==> Backup directory missing: %s", state.BackupPath))
log(" Original configs cannot be restored (backup was deleted)")
} else {
log(fmt.Sprintf("==> Restoring backup from %s", state.BackupPath))
if !opts.DryRun {
if err := restoreBackup(state.BackupPath, log); err != nil {
log(fmt.Sprintf(" Warning: backup restore failed: %v", Repobility · MCP-ready · https://repobility.com
removeManagedBlock function · go · L180-L192 (13 LOC)internal/uninstaller/uninstaller.go
func removeManagedBlock(content string) string {
startIdx := strings.Index(content, zshrcMarkerStart)
endIdx := strings.Index(content, zshrcMarkerEnd)
if startIdx == -1 || endIdx == -1 || endIdx < startIdx {
return content
}
before := content[:startIdx]
after := content[endIdx+len(zshrcMarkerEnd):]
if len(after) > 0 && after[0] == '\n' {
after = after[1:]
}
return before + after
}removePackages function · go · L194-L235 (42 LOC)internal/uninstaller/uninstaller.go
func removePackages(p *tea.Program, opts Options) error {
log := func(text string) { p.Send(tui.LogLine{Text: text}) }
if opts.KeepPackages {
log("==> Keeping packages (--keep-packages)")
return nil
}
state, err := installer.LoadState()
if err != nil {
return err
}
if len(state.InstalledPackages) == 0 {
log(" No packages were installed by Omachy")
return nil
}
// Remove in reverse order — only packages Omachy installed
for i := len(state.InstalledPackages) - 1; i >= 0; i-- {
pkg := state.InstalledPackages[i]
if opts.DryRun {
log(fmt.Sprintf("==> Would uninstall %s", pkg.Name))
continue
}
if err := brew.Uninstall(pkg.Name, pkg.Cask, log); err != nil {
log(fmt.Sprintf(" Warning: %v", err))
}
}
// Remove taps that Omachy added
for _, tap := range state.InstalledTaps {
if opts.DryRun {
log(fmt.Sprintf("==> Would untap %s", tap))
continue
}
log(fmt.Sprintf("==> Untapping %s", tap))
shell.Run("brew", "untap", tap)
}
returnrestoreDefaults function · go · L237-L294 (58 LOC)internal/uninstaller/uninstaller.go
func restoreDefaults(p *tea.Program, opts Options) error {
log := func(text string) { p.Send(tui.LogLine{Text: text}) }
state, err := installer.LoadState()
if err != nil {
return err
}
log("==> Restoring macOS defaults")
for key, stored := range state.OriginalDefaults {
parts := strings.SplitN(key, ":", 2)
if len(parts) != 2 {
continue
}
domain, defKey := parts[0], parts[1]
// Stored format is "type:value" (e.g. "-bool:1") or legacy plain value
typ, value := parseStoredDefault(stored)
if opts.DryRun {
log(fmt.Sprintf(" Would restore %s %s → %s %s", domain, defKey, typ, value))
continue
}
_, err := shell.Run("defaults", "write", domain, defKey, typ, value)
if err != nil {
log(fmt.Sprintf(" Warning: could not restore %s: %v", defKey, err))
} else {
log(fmt.Sprintf(" Restored %s", defKey))
}
}
// Delete any defaults Omachy sets that have no saved original —
// these didn't exist before Omachy and should be removed, not left restoreBackup function · go · L296-L322 (27 LOC)internal/uninstaller/uninstaller.go
func restoreBackup(backupDir string, onLine func(string)) error {
home, _ := os.UserHomeDir()
return filepath.WalkDir(backupDir, func(path string, d os.DirEntry, err error) error {
if err != nil || d.IsDir() {
return err
}
rel, _ := filepath.Rel(backupDir, path)
dest := filepath.Join(home, rel)
if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
return err
}
data, err := os.ReadFile(path)
if err != nil {
return err
}
info, _ := d.Info()
mode := info.Mode()
onLine(fmt.Sprintf(" Restoring %s", rel))
return os.WriteFile(dest, data, mode)
})
}parseStoredDefault function · go · L326-L335 (10 LOC)internal/uninstaller/uninstaller.go
func parseStoredDefault(stored string) (typ, value string) {
// Check for known type prefixes
for _, prefix := range []string{"-bool:", "-int:", "-float:", "-string:"} {
if strings.HasPrefix(stored, prefix) {
return stored[:len(prefix)-1], stored[len(prefix):]
}
}
// Legacy format: no type stored, treat as string
return "-string", stored
}shortPath function · go · L337-L343 (7 LOC)internal/uninstaller/uninstaller.go
func shortPath(path string) string {
home, _ := os.UserHomeDir()
if strings.HasPrefix(path, home) {
return "~" + path[len(home):]
}
return path
}main function · go · L14-L22 (9 LOC)main.go
func main() {
cmd.Version = version
installer.EmbeddedConfigs = Configs
if err := cmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}‹ prevpage 2 / 2