first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:52:23 +03:00
commit 880f412e2c
2662 changed files with 866266 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
package logo
import (
"fmt"
"strings"
)
const compact = "BIFROST CLI"
// Render returns the ASCII logo for the given terminal width.
func Render(width int) string {
if width < 61 {
return compact
}
return strings.Join([]string{
"╔═══════════════════════════════════════════════════════════╗",
"║ ║",
"║ ██████╗ ██╗███████╗██████╗ ██████╗ ███████╗████████╗ ║",
"║ ██╔══██╗██║██╔════╝██╔══██╗██╔═══██╗██╔════╝╚══██╔══╝ ║",
"║ ██████╔╝██║█████╗ ██████╔╝██║ ██║███████╗ ██║ ║",
"║ ██╔══██╗██║██╔══╝ ██╔══██╗██║ ██║╚════██║ ██║ ║",
"║ ██████╔╝██║██║ ██║ ██║╚██████╔╝███████║ ██║ ║",
"║ ╚═════╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ║",
"║ ║",
"║═══════════════════════════════════════════════════════════║",
"║ CLI ║",
"║═══════════════════════════════════════════════════════════║",
"║ https://github.com/maximhq/bifrost ║",
"╚═══════════════════════════════════════════════════════════╝",
}, "\n")
}
// BootHeader builds the full boot header with the ASCII logo and version info.
func BootHeader(width int, version, commit, source string, noColor bool) string {
if width < 61 {
meta := fmt.Sprintf("%s (%s)", version, commit)
return fmt.Sprintf("\n\n%s\n%s", Render(width), meta)
}
meta := fmt.Sprintf("%s (%s) config=%s", version, commit, source)
var b strings.Builder
b.WriteString("\n\n")
b.WriteString(Render(width))
b.WriteString("\n")
if noColor {
b.WriteString(meta)
} else {
b.WriteString("\033[2;36m" + meta + "\033[0m")
}
b.WriteString("\n")
return b.String()
}

View File

@@ -0,0 +1,30 @@
package logo
import (
"strings"
"testing"
)
func TestRenderLarge(t *testing.T) {
got := Render(120)
if !strings.Contains(got, "██████╗") {
t.Fatalf("expected large logo, got %q", got)
}
}
func TestRenderCompact(t *testing.T) {
got := Render(20)
if got != "BIFROST CLI" {
t.Fatalf("expected compact logo, got %q", got)
}
}
func TestBootHeaderStartsWithLogo(t *testing.T) {
header := BootHeader(120, "v1", "abc", "none", true)
if !strings.Contains(header, Render(120)) {
t.Fatalf("expected boot header to contain logo")
}
if !strings.Contains(header, "config=none") {
t.Fatalf("expected boot header to contain config source")
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,72 @@
package tui
import (
"os"
"strings"
"testing"
tea "github.com/charmbracelet/bubbletea"
)
func TestPrefersPlainChooserLayoutAppleTerminal(t *testing.T) {
old := os.Getenv("TERM_PROGRAM")
t.Cleanup(func() {
if old == "" {
os.Unsetenv("TERM_PROGRAM")
return
}
os.Setenv("TERM_PROGRAM", old)
})
os.Setenv("TERM_PROGRAM", "Apple_Terminal")
if !prefersPlainChooserLayout() {
t.Fatal("expected Apple Terminal to use the plain chooser layout")
}
os.Setenv("TERM_PROGRAM", "iTerm.app")
if prefersPlainChooserLayout() {
t.Fatal("did not expect iTerm to use the plain chooser layout")
}
}
func TestRenderPlainChooserView(t *testing.T) {
out := renderPlainChooserView("Ready", "base url\nmodel", "enter launch")
for _, want := range []string{"BIFROST CLI", "Ready", "base url", "model", "enter launch"} {
if !strings.Contains(out, want) {
t.Fatalf("expected output to contain %q, got %q", want, out)
}
}
}
func TestChooserViewShowsUpdatePrompt(t *testing.T) {
m := newChooserModel(ChooserConfig{
Version: "v1.0.0",
Commit: "abc123",
ConfigSrc: "test",
UpdateVersion: "v1.2.3",
})
view := m.View()
for _, want := range []string{"Update available:", "bifrost v1.2.3", "press y to update now"} {
if !strings.Contains(view, want) {
t.Fatalf("expected chooser view to contain %q, got %q", want, view)
}
}
}
func TestChooserUpdateShortcutRequestsUpdate(t *testing.T) {
m := newChooserModel(ChooserConfig{
UpdateVersion: "v1.2.3",
})
// Move to a non-text-entry phase so 'y' isn't consumed by the input field.
m.phase = phaseSummary
next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'y'}})
got := next.(chooserModel)
if !got.updateRequested {
t.Fatal("expected y to request update when update is available")
}
}

View File

@@ -0,0 +1,134 @@
package tui
import (
"fmt"
"os"
"strings"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
// confirmModel holds information about current model selection
type confirmModel struct {
header string
prompt string
command string
idx int
quit bool
yes bool
clearFirst bool
}
// runConfirm runs a confirm dialog with the given model and returns the user's
// choice.
func runConfirm(m confirmModel) (bool, error) {
p := tea.NewProgram(
m,
tea.WithInput(os.Stdin),
tea.WithOutput(os.Stdout),
tea.WithInputTTY(),
)
out, err := p.Run()
if err != nil {
return false, err
}
fm, ok := out.(confirmModel)
if !ok {
return false, fmt.Errorf("unexpected model type from tui")
}
if fm.quit {
return false, nil
}
return fm.yes, nil
}
// RunConfirmInstall displays a yes/no confirmation dialog asking the user
// whether to install a missing harness. Returns true if the user confirms.
func RunConfirmInstall(header, harnessLabel, command string) (bool, error) {
return runConfirm(confirmModel{
header: header,
prompt: fmt.Sprintf("%s is not installed. Install now?", harnessLabel),
command: command,
})
}
// RunConfirmSettings displays a yes/no confirmation dialog asking the user
// whether to update the harness's native settings file. Returns true if the
// user confirms.
func RunConfirmSettings(harnessLabel, settingsPath string) (bool, error) {
return runConfirm(confirmModel{
prompt: fmt.Sprintf("Update %s settings? (%s)", harnessLabel, settingsPath),
clearFirst: true,
})
}
// Init implements tea.Model.
func (m confirmModel) Init() tea.Cmd {
if m.clearFirst {
return tea.ClearScreen
}
return nil
}
// Update implements tea.Model. Handles y/n, arrow keys, and enter for confirmation.
func (m confirmModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
s := msg.String()
switch s {
case "ctrl+c", "q", "esc":
m.quit = true
return m, tea.Quit
case "left", "h", "right", "l", "tab":
if m.idx == 0 {
m.idx = 1
} else {
m.idx = 0
}
return m, nil
case "y":
m.yes = true
return m, tea.Quit
case "n":
m.yes = false
return m, tea.Quit
case "enter":
m.yes = m.idx == 0
return m, tea.Quit
}
}
return m, nil
}
// View implements tea.Model. Renders the confirmation prompt with Yes/No buttons.
func (m confirmModel) View() string {
selected := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("42"))
normal := lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
yes := normal.Render("[ Yes ]")
no := normal.Render("[ No ]")
if m.idx == 0 {
yes = selected.Render("[ Yes ]")
} else {
no = selected.Render("[ No ]")
}
var b strings.Builder
if m.header != "" {
b.WriteString(m.header)
b.WriteString("\n")
}
b.WriteString(m.prompt)
b.WriteString("\n")
if m.command != "" {
b.WriteString("command: ")
b.WriteString(m.command)
b.WriteString("\n")
}
b.WriteString("\n")
b.WriteString(yes + " " + no)
b.WriteString("\n")
b.WriteString("enter: confirm, y/n quick choice, q: cancel")
return b.String()
}