Skip to content

Styled CLI with charmbracelet/fang + Charm v2 stack#29

Merged
munezaclovis merged 15 commits intomainfrom
refactor/fang-ui
Mar 7, 2026
Merged

Styled CLI with charmbracelet/fang + Charm v2 stack#29
munezaclovis merged 15 commits intomainfrom
refactor/fang-ui

Conversation

@munezaclovis
Copy link
Contributor

Summary

  • Upgrade to Charm v2 stack: lipgloss v2, huh v2, add fang v2 for styled help/usage/error output
  • Wire fang into root: replace manual Execute() error handling with fang.Execute() — all help pages, usage text, and errors are now beautifully styled with syntax-highlighted examples, colored sections, and an ERROR badge
  • Simplify error handling across 16 command files: convert ui.Fail() + ErrAlreadyPrinted + SilenceUsage sandwich pattern (~45 lines each) to plain return fmt.Errorf() — fang renders them automatically
  • Add Example: fields to 8 key commands (link, install, env, log, php:install, php:use, unlink, service:add) — fang syntax-highlights program names, flags, args, and comments
  • Remove ~100 net lines of manual spacing, unused imports, dead code (ui.Fatal)

Before → After

Help output: default ugly Cobra → styled sections (USAGE, EXAMPLES, COMMANDS, FLAGS) with colored code blocks

Error output: manual ui.Fail() + FailDetail() + blank lines + SilenceUsage → just return fmt.Errorf(), fang renders ERROR badge + contextual "Try --help" hint

What stays

internal/ui/ spinners, progress bars, tables, Success(), Subtle() — these handle runtime output that fang doesn't touch. ErrAlreadyPrinted remains for spinner failures (custom fang error handler skips them).

Follow-up work

  • Replace hand-built tables in status.go, service_status.go, php_list.go with ui.Table()
  • Replace hand-rolled spinner/progress with charm bubbletea equivalents (optional)
  • Add Example: fields to more commands (incremental)

Test plan

  • go build ./... passes
  • go test ./... passes (all packages)
  • pv --help renders styled output
  • pv link --help shows syntax-highlighted examples
  • pv --bogus shows fang ERROR badge
  • pv --version shows version

- Migrate lipgloss v1 → v2 (charm.land/lipgloss/v2)
- Migrate huh v0.8 → v2 (charm.land/huh/v2)
- Add fang v2 (charm.land/fang/v2) for styled help/usage/errors
- Replace manual Execute() error handling with fang.Execute()
- Add pv color scheme using charmtone palette
- Move examples from install.go Long field to proper Example field
- Clean up env.go Long field, add Example
- Add examples to: link, unlink, log, php:install, php:use, service:add
- Convert 16 files from ui.Fail/ErrAlreadyPrinted sandwich to plain
  fmt.Errorf returns; fang's DefaultErrorHandler renders the ERROR badge
- Add custom pvErrorHandler that skips ErrAlreadyPrinted (for ui.Step
  failures where the spinner already displayed the error)
- Change ui.Step/StepVerbose/StepProgress to return ErrAlreadyPrinted
  on failure since they display the error inline
- Remove all cmd.SilenceUsage = true (fang sets it globally)
- Remove installCmd.SilenceUsage from init
- Clean up unused imports (os, ui) in simplified files
- Remove bare fmt.Fprintln(os.Stderr) padding from 9 command files
- Simplify daemon enable/disable to return ui.Step() directly
- Clean up unused os imports after spacing removal
Document the fang/huh/lipgloss/ui layering, error handling patterns,
and hard don'ts now that the Charm v2 stack is in place.
lipgloss v2 always emits ANSI escape codes in Render(), even when
stderr is piped or captured. This broke "8.4 (default)" matching in
start-curl.sh because the two words use different styles with ANSI
reset/re-style codes between them.
- Guard ErrAlreadyPrinted in update.go, install.go, service_add.go to
  avoid printing empty error messages after ui.Step already displayed them
- Run go mod tidy to mark fang/v2 and charmtone as direct dependencies
- Fix duplicate Example in env.go
- Remove double blank lines in service_destroy.go, service_remove.go
- Fix CLAUDE.md: Footer signature, soften fmt.Print rule to match reality
Split the vague "prefer ui.* helpers" rule into two clear rules:
errors always return to fang, status output uses ui.* helpers.
- setup.go: 6x ui.Red.Render("!") → ui.Fail() with ErrAlreadyPrinted guards
- uninstall.go: 4x sub-command errors → ui.Fail() with guards,
  6x post-ui.Step error prints removed (were printing empty sentinel)
- link.go, unlink.go, php_use.go: ui.Red.Render("!") → ui.Fail()
- update.go: add missing ErrAlreadyPrinted guard on self-update
- doctor.go: all output was going to stdout instead of stderr
- service_logs.go: fmt.Fprintf → ui.Subtle()
- Add Cobra command groups (Core, Server, PHP, Composer, Mago, Colima,
  Services, Daemon) with GroupID on every command so `pv -h` renders
  organized, readable sections instead of a flat list.
- Refactor uninstall.go: replace bufio.Scanner with huh interactive
  prompts, replace all raw fmt.Fprint* with ui.Subtle/ui.Success/ui.Fail
  per CLAUDE.md guidelines.
…packages

Restructure flat cmd/ files into grouped subpackages under
internal/commands/{php,mago,composer,colima,service,daemon}/.
Each group exports Register() for command registration and Run*()
wrappers for orchestrator access. Thin shims in cmd/ call Register().

Also fix UI guideline violations in service status/remove commands:
replace raw fmt.Fprintf with ui.Table, remove blank-line spacers.
huh opens /dev/tty directly, so piping stdin doesn't work in CI.
Add -f/--force flag that skips the confirmation and auth backup
prompts. Update e2e script to use --force instead of piping.
- Fix nil pointer dereference in uninstall when settings file is missing
- Eliminate command injection in runSudo by using exec.Command with
  separate argv instead of shell interpolation via sudo sh -c
- Tighten copyFile permissions from 0644 to 0600 for auth token backup
- Replace raw fmt.Fprintf in doctor.go with new ui.SectionHeader helper
- Deduplicate sanitizeProjectName into services.SanitizeProjectName
- Add ui.Subtle message when service:env skips unknown services
- Improve ErrAlreadyPrinted sentinel with descriptive error message
- Update CLAUDE.md to document internal/commands/<group>/ subpackages
- Add registration tests for all 6 command subpackages
- Add ui.Step/StepVerbose/StepProgress contract tests
@munezaclovis munezaclovis merged commit cd0715e into main Mar 7, 2026
1 check passed
@munezaclovis munezaclovis deleted the refactor/fang-ui branch March 7, 2026 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant