cli

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 15, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Index

Examples

Constants

View Source
const (
	// SectionBreak adds visual separation between major sections
	SectionBreak = "\n"

	// SubsectionBreak adds minimal separation within sections
	SubsectionBreak = ""
)

Section spacing for CLI output

Variables

View Source
var (
	ColorPrimary   = theme.ColorPrimary
	ColorSecondary = theme.ColorSecondary
	ColorAccent    = theme.ColorAccent
	ColorSuccess   = theme.ColorSuccess
	ColorWarning   = theme.ColorWarning
	ColorError     = theme.ColorError
	ColorInfo      = theme.ColorInfo
	ColorDim       = theme.ColorDim
	ColorPublic    = theme.ColorPublic
	ColorPrivate   = theme.ColorPrivate
)

Re-export theme colors for backward compatibility

View Source
var (
	StyleBrand = lipgloss.NewStyle().
				Foreground(ColorPrimary).
				Bold(true)

	StyleSecondary = lipgloss.NewStyle().
					Foreground(ColorSecondary)

	StyleAccent = lipgloss.NewStyle().
				Foreground(ColorAccent)

	StyleSuccess = lipgloss.NewStyle().
					Foreground(ColorSuccess)

	StyleWarning = lipgloss.NewStyle().
					Foreground(ColorWarning)

	StyleError = lipgloss.NewStyle().
				Foreground(ColorError)

	StyleInfo = lipgloss.NewStyle().
				Foreground(ColorInfo)

	StyleDim = lipgloss.NewStyle().
				Foreground(ColorDim)

	StyleBold = lipgloss.NewStyle().
				Bold(true)

	StyleCommand = lipgloss.NewStyle().
					Foreground(ColorSecondary)

	StyleFlag = lipgloss.NewStyle().
				Foreground(ColorInfo)

	StyleGroupHeader = lipgloss.NewStyle().
						Foreground(ColorPrimary).
						Bold(true)

	// StyleFile for file/directory paths (uses accent color for visual distinction)
	StyleFile = lipgloss.NewStyle().
				Foreground(ColorAccent)

	// SpinnerStyle for spinner animations
	SpinnerStyle = lipgloss.NewStyle().
					Foreground(ColorPrimary)

	// StyleCallout for contextual highlights (stars, step indicators, guided actions)
	// Used to draw attention to recommended next actions based on user state
	StyleCallout = lipgloss.NewStyle().
					Foreground(ColorAccent)

	// StyleCalloutBold for emphasized callout elements (command names in guided actions)
	StyleCalloutBold = lipgloss.NewStyle().
						Foreground(ColorSecondary).
						Bold(true)

	// Visibility styles (public=teal, private=amber)
	StylePublic = lipgloss.NewStyle().
				Foreground(ColorPublic)

	StylePrivate = lipgloss.NewStyle().
					Foreground(ColorPrivate)
)

Text styles

View Source
var ErrHeadless = errors.New("cannot open browser: no graphical display available (SSH or headless environment)")

ErrHeadless is returned by OpenInBrowser when the environment has no graphical display (SSH session, no DISPLAY). Callers should handle this to provide context-appropriate guidance (e.g., upload to view online, or copy the file).

View Source
var ErrSilent = SilentError{}

ErrSilent is a sentinel error indicating output was already printed.

View Source
var Styles = struct {
	Success   lipgloss.Style
	Preserved lipgloss.Style
	Error     lipgloss.Style
	Warning   lipgloss.Style
	Info      lipgloss.Style
	Hint      lipgloss.Style
	Code      lipgloss.Style
}{
	Success:   successStyle,
	Preserved: preservedStyle,
	Error:     errorStyle,
	Warning:   warningStyle,
	Info:      infoStyle,
	Hint:      hintStyle,
	Code:      codeStyle,
}

Styles exposes lipgloss styles for use in commands

Functions

func ColumnWidths

func ColumnWidths(rows [][]string, minWidths []int, maxWidths []int) []int

ColumnWidths calculates optimal widths for columns based on content

func ConfirmDangerousOperation

func ConfirmDangerousOperation(operationName, exactMatch string, force bool) error

ConfirmDangerousOperation is a generic confirmation prompt for destructive operations Requires user to type exactMatch string to proceed Returns error if user cancels or input doesn't match

Example

Example demonstrates generic dangerous operation confirmation

package main

import (
	"fmt"

	"github.com/sageox/ox/internal/cli"
)

func main() {
	// for operations where you want user to type a specific string
	operation := "delete all production data"
	confirmText := "DELETE-PROD"

	if err := cli.ConfirmDangerousOperation(operation, confirmText, false); err != nil {
		fmt.Println("Operation canceled")
		return
	}

	fmt.Println("User confirmed, proceeding...")
}

func ConfirmUninstall

func ConfirmUninstall(repoName string, force bool) error

ConfirmUninstall prompts user to confirm a destructive uninstall operation Returns error if user cancels or input fails, nil if confirmed Bypasses prompt if force is true

Example

Example demonstrates how to use the uninstall confirmation flow

package main

import (
	"fmt"

	"github.com/sageox/ox/internal/cli"
)

func main() {
	// in actual use, these would come from scanning the repository
	items := []cli.RemovalItem{
		{
			Type:        "directory",
			Path:        ".sageox",
			Description: "SageOx state and configuration",
		},
		{
			Type:        "file",
			Path:        "AGENTS.md",
			Description: "agent guidance document",
		},
		{
			Type:        "file",
			Path:        ".claude/CLAUDE.md",
			Description: "Claude-specific guidance",
		},
		{
			Type:        "hook",
			Path:        ".git/hooks/pre-commit",
			Description: "git pre-commit hook",
		},
	}

	// show what will be removed
	cli.ShowUninstallPreview(items, false)

	// display warning
	cli.DangerousOperationWarning("UNINSTALL SAGEOX", "my-awesome-repo")

	// get confirmation (would use force flag from command line)
	force := false // in real code: cmd.Flags().GetBool("force")
	if err := cli.ConfirmUninstall("my-awesome-repo", force); err != nil {
		fmt.Println("Uninstall canceled")
		return
	}

	fmt.Println("Proceeding with uninstall...")
	// ... actual uninstall logic here ...
}

func ConfirmYesNo

func ConfirmYesNo(prompt string, defaultYes bool) bool

ConfirmYesNo displays a yes/no prompt and loops until valid input. Returns true if user confirms with y/yes, false for n/no. Empty input uses the default specified by defaultYes.

Example

Example demonstrates yes/no prompts

package main

import (
	"fmt"

	"github.com/sageox/ox/internal/cli"
)

func main() {
	// simple yes/no with default to No (safer for destructive ops)
	if cli.ConfirmYesNo("Remove user-level config too?", false) {
		fmt.Println("Will remove user config")
	}

	// with default to Yes (for non-destructive confirmations)
	if cli.ConfirmYesNo("Install recommended plugins?", true) {
		fmt.Println("Will install plugins")
	}
}

func DangerousOperationWarning

func DangerousOperationWarning(operation, target string)

DangerousOperationWarning displays a styled warning box for destructive operations

func FormatRow

func FormatRow(row []string, widths []int) string

FormatRow formats a row with dynamic widths

func FormatSlogLine added in v0.4.0

func FormatSlogLine(line string) string

FormatSlogLine parses a raw slog log line and returns a colorized version. Malformed lines are returned as-is.

func FormatTipText

func FormatTipText(tip string) string

FormatTipText formats tip text by highlighting backtick-wrapped commands

func HasMultipleEndpoints

func HasMultipleEndpoints(sageoxDir string) bool

HasMultipleEndpoints is a quick check for whether endpoint selection is needed.

func InputWithDefault

func InputWithDefault(title, defaultVal string) (string, error)

InputWithDefault prompts for text input with a placeholder showing the default. If user enters empty string, returns the default value. Falls back to simple text prompt when stdin is not a TTY or in non-interactive mode.

func IsHeadless

func IsHeadless() bool

IsHeadless returns true when no graphical display is available. Detects SSH sessions and missing display servers so callers can skip browser-open calls that would launch broken text-mode browsers (lynx, w3m).

func IsInteractive

func IsInteractive() bool

IsInteractive returns true if interactive mode is enabled. Interactive mode is disabled when --no-interactive flag is set, CI=true, or stdin is not a terminal (e.g., running inside an AI agent).

func IsSilent

func IsSilent(err error) bool

IsSilent checks if an error is a silent error (already displayed).

func OpenInBrowser

func OpenInBrowser(url string) error

OpenInBrowser opens a URL in the user's default browser. Returns ErrHeadless if the environment is headless (SSH, no display). Silently returns nil when SKIP_BROWSER=1 (used by automated tests and demo scripts to suppress browser popups during non-interactive runs). This is the single entry point for browser-opening across the CLI.

func PrintActionHint

func PrintActionHint(command, description string, step int)

PrintActionHint prints a prominent actionable hint with star, command, and optional step. Matches the visual styling used in help output for contextual next-action guidance. Example: "★ ox login Authenticate with SageOx (Step 1)"

func PrintDisclaimer

func PrintDisclaimer()

PrintDisclaimer prints the Claude Code compatibility disclaimer in brand gold.

func PrintError

func PrintError(msg string)

func PrintHint

func PrintHint(msg string)

PrintHint prints a dimmed hint message (e.g., "Run 'ox login' to authenticate")

func PrintInfo

func PrintInfo(msg string)

func PrintJSON

func PrintJSON(v any)

func PrintPreserved

func PrintPreserved(msg string)

PrintPreserved prints a message indicating an existing file was preserved (not overwritten). Uses cyan color to distinguish from green success (created) messages.

func PrintSectionBreak

func PrintSectionBreak()

PrintSectionBreak prints consistent spacing between sections

func PrintSubsectionBreak

func PrintSubsectionBreak()

PrintSubsectionBreak prints minimal spacing within sections

func PrintSuccess

func PrintSuccess(msg string)

func PrintSuggestionBox

func PrintSuggestionBox(title, message, fix string)

PrintSuggestionBox prints a bordered suggestion box to stderr.

func PrintTip

func PrintTip(tip string)

PrintTip prints a helpful tip message with command highlighting

func PrintWarning

func PrintWarning(msg string)

func SelectEndpoint

func SelectEndpoint(endpoints []EndpointInfo, defaultEndpoint, flagEndpoint string) (string, error)

SelectEndpoint prompts the user to select an endpoint when multiple are available. If only one endpoint exists, returns it without prompting. If flagEndpoint is provided, validates it exists and returns it. Returns the selected endpoint URL.

func SelectOne

func SelectOne(title string, options []string, defaultIdx int) (int, error)

SelectOne displays an interactive selection menu with arrow key navigation. Returns the index of the selected option (0-based) or -1 if canceled. Falls back to numbered prompt when stdin is not a TTY or in non-interactive mode.

func SelectOneValue

func SelectOneValue[T comparable](title string, options []SelectOption[T], defaultIdx int) (T, error)

SelectOneValue displays an interactive selection menu and returns the selected value.

func SetJSONMode

func SetJSONMode(enabled bool)

func SetNoInteractive

func SetNoInteractive(enabled bool)

SetNoInteractive sets the global non-interactive mode flag. When enabled, spinners and TUI elements are disabled.

func ShowUninstallPreview

func ShowUninstallPreview(items []RemovalItem, dryRun bool)

ShowUninstallPreview displays what will be removed, grouped by type Shows summary first, then details if items exist

Example (DryRun)

Example demonstrates dry-run mode

package main

import (
	"github.com/sageox/ox/internal/cli"
)

func main() {
	items := []cli.RemovalItem{
		{Type: "directory", Path: ".sageox", Description: "state directory"},
		{Type: "file", Path: "AGENTS.md", Description: "guidance"},
	}

	// dry-run mode shows what would be removed without doing it
	cli.ShowUninstallPreview(items, true)
}

func StreamFormattedLogs added in v0.4.0

func StreamFormattedLogs(r io.Reader, w io.Writer)

StreamFormattedLogs reads log lines from r, colorizes each, and writes to w. Blocks until r is closed or an error occurs.

func SuggestionBox

func SuggestionBox(title, message, fix string) string

SuggestionBox renders a bordered box with a title, message, and fix command. Used for actionable suggestions that need visual emphasis.

func TruncateID

func TruncateID(id string, verbose bool) string

TruncateID shortens an ID for display, keeping prefix and suffix Examples:

  • "oxsid_01JBCDEFGHIJKLMNOPQRSTUVWXYZ" -> "oxsid_01JB...WXYZ" (verbose=false)
  • "oxsid_01JBCDEFGHIJKLMNOPQRSTUVWXYZ" -> "oxsid_01JBCDEFGHIJKLMNOPQRSTUVWXYZ" (verbose=true)
  • "Oxa7b3" -> "Oxa7b3" (already short)

Returns full ID if verbose=true or ID is already short (<=16 chars).

func TruncateUUID

func TruncateUUID(uuid string, verbose bool) string

TruncateUUID shortens a UUID for display Examples:

  • "a1b2c3d4-e5f6-7890-abcd-ef1234567890" -> "a1b2c3d4..." (verbose=false)
  • "a1b2c3d4-e5f6-7890-abcd-ef1234567890" -> "a1b2c3d4-e5f6-7890-abcd-ef1234567890" (verbose=true)

Returns full UUID if verbose=true or UUID is already short (<=12 chars).

func WithSpinner

func WithSpinner[T any](message string, fn func() (T, error)) (T, error)

WithSpinner runs an async function with a spinner displayed. Shows spinner after a short delay to avoid flicker for fast operations. In non-interactive mode (CI or --no-interactive), runs without spinner. Returns the result of the function.

func WithSpinnerNoResult

func WithSpinnerNoResult(message string, fn func() error) error

WithSpinnerNoResult runs an async function with a spinner displayed. Use when the function returns only an error.

func Wordmark

func Wordmark() string

Wordmark returns the two-tone "SageOx" brand wordmark as a rendered string.

Types

type Context

type Context struct {
	// Config holds the loaded configuration
	Config *config.Config

	// Logger provides structured logging
	Logger *slog.Logger

	// Telemetry client for tracking command usage
	TelemetryClient *telemetry.Client

	// CommandStartTime tracks when the command started (for telemetry)
	CommandStartTime time.Time

	// Ctx provides the Go context for cancellation and timeouts
	Ctx context.Context
}

Context holds common dependencies for CLI commands. It provides centralized initialization and access to shared resources like configuration, logging, and telemetry.

func NewContext

func NewContext(cmd *cobra.Command, args []string) (*Context, error)

NewContext creates a new CLI context from a Cobra command. This centralizes all the initialization logic that was previously scattered across PersistentPreRunE in root.go.

func (*Context) Fprintf

func (c *Context) Fprintf(w *os.File, format string, args ...any)

Fprintf writes formatted output to a writer (useful for stderr)

func (*Context) IsJSON

func (c *Context) IsJSON() bool

IsJSON returns true if JSON output mode is enabled

func (*Context) IsNoInteractive

func (c *Context) IsNoInteractive() bool

IsNoInteractive returns true if non-interactive mode is enabled (CI or --no-interactive)

func (*Context) IsQuiet

func (c *Context) IsQuiet() bool

IsQuiet returns true if quiet mode is enabled

func (*Context) IsReview

func (c *Context) IsReview() bool

IsReview returns true if review (security audit) mode is enabled

func (*Context) IsText

func (c *Context) IsText() bool

IsText returns true if text output mode is enabled

func (*Context) IsVerbose

func (c *Context) IsVerbose() bool

IsVerbose returns true if verbose logging is enabled

func (*Context) LogDebug

func (c *Context) LogDebug(msg string, args ...any)

LogDebug logs a debug-level message

func (*Context) LogError

func (c *Context) LogError(msg string, args ...any)

LogError logs an error-level message

func (*Context) LogInfo

func (c *Context) LogInfo(msg string, args ...any)

LogInfo logs an info-level message

func (*Context) LogWarn

func (c *Context) LogWarn(msg string, args ...any)

LogWarn logs a warning-level message

func (*Context) PrintError

func (c *Context) PrintError(msg string)

PrintError prints an error message

func (*Context) PrintPreserved

func (c *Context) PrintPreserved(msg string)

PrintPreserved prints a preserved/unchanged message (unless quiet mode)

func (*Context) PrintSuccess

func (c *Context) PrintSuccess(msg string)

PrintSuccess prints a success message (unless quiet mode)

func (*Context) PrintWarning

func (c *Context) PrintWarning(msg string)

PrintWarning prints a warning message (unless quiet mode)

func (*Context) Printf

func (c *Context) Printf(format string, args ...any)

Printf prints a formatted message to stdout (respects quiet/json modes)

func (*Context) Println

func (c *Context) Println(msg string)

Println prints a message to stdout (respects quiet/json modes)

func (*Context) Shutdown

func (c *Context) Shutdown()

Shutdown performs cleanup operations. Should be called in PersistentPostRunE or deferred.

func (*Context) TrackCommandCompletion

func (c *Context) TrackCommandCompletion(cmd *cobra.Command)

TrackCommandCompletion tracks a successful command completion via telemetry

func (*Context) TrackCommandError

func (c *Context) TrackCommandError(cmd *cobra.Command, err error)

TrackCommandError tracks a command error via telemetry

type EndpointInfo

type EndpointInfo struct {
	Endpoint string
	RepoID   string
	InitAt   string
}

EndpointInfo holds information about an endpoint from a repo marker

func DiscoverEndpoints

func DiscoverEndpoints(sageoxDir string) ([]EndpointInfo, error)

DiscoverEndpoints scans .sageox/ for .repo_* marker files and returns unique endpoints found. Returns endpoints sorted by their first appearance.

type RemovalItem

type RemovalItem struct {
	Type        string // e.g., "directory", "file", "hook"
	Path        string // relative path from repo root
	Description string // brief description of what this is
}

RemovalItem represents an item to be removed during uninstall

type SelectOption

type SelectOption[T any] struct {
	Label string
	Value T
}

SelectOption represents a single option in a selection menu.

type SilentError

type SilentError struct{}

SilentError is an error type that signals the error was already displayed. main.go checks for this and skips printing "Error:" prefix.

func (SilentError) Error

func (e SilentError) Error() string

type SlogLine added in v0.4.0

type SlogLine struct {
	Time    string // raw timestamp value
	Level   string // DEBUG, INFO, WARN, ERROR
	Message string // msg value (unquoted)
	Attrs   string // remaining key=value pairs
}

SlogLine holds parsed fields from a slog TextHandler line.

func ParseSlogLine added in v0.4.0

func ParseSlogLine(line string) (SlogLine, bool)

ParseSlogLine parses a slog TextHandler line into its components. Returns false if the line doesn't match the expected format.

type SpinnerResult

type SpinnerResult[T any] struct {
	Value T
	Err   error
}

SpinnerResult holds the result of an async operation

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL