← back to dough654__omachy

Function bodies 96 total

All specs Real LLM only Function bodies
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 a
View 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 ch
renderFlags 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) * 10
stopServices 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)
	}

	return
restoreDefaults 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