Signed-off-by: Hadi <[email protected]>
This commit is contained in:
Hadi
2026-08-27 00:05:08 +02:00
commit 624a723b16
29 changed files with 1989 additions and 0 deletions
+93
View File
@@ -0,0 +1,93 @@
package main
import (
"strings"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"github.com/anotherhadi/ilovetui/app"
"github.com/anotherhadi/ilovetui/minsize"
"github.com/anotherhadi/ilovetui/modal"
"github.com/anotherhadi/ilovetui/notification"
zone "github.com/lrstanley/bubblezone/v2"
"github.com/anotherhadi/default-creds-tui/internal/creds"
"github.com/anotherhadi/default-creds-tui/ui"
)
const (
minWidth = 25
minHeight = 15
)
type appModel struct {
core ui.Model
modals modal.Model
notif notification.Model
minsize minsize.Model
width, height int
}
func newApp(mode creds.Mode, apiURL string, printOnEnter bool) appModel {
zone.NewGlobal()
return appModel{
core: ui.New(mode, apiURL, printOnEnter),
modals: modal.New(),
notif: notification.New(),
minsize: minsize.New(minWidth, minHeight),
}
}
func (a appModel) Init() tea.Cmd {
return tea.Batch(a.core.Init(), a.modals.Init(), a.notif.Init())
}
func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if _, ok := msg.(app.QuitMsg); ok {
return a, tea.Quit
}
if sizeMsg, ok := msg.(tea.WindowSizeMsg); ok {
a.width, a.height = sizeMsg.Width, sizeMsg.Height
}
a.core.ModalOpen = a.modals.Open()
var coreCmd tea.Cmd
a.core, coreCmd = a.core.Update(msg)
var modalCmd, notifCmd tea.Cmd
a.modals, modalCmd = a.modals.Update(msg)
a.notif, notifCmd = a.notif.Update(msg)
return a, tea.Batch(coreCmd, modalCmd, notifCmd)
}
func (a appModel) View() tea.View {
if !a.minsize.Fits(a.width, a.height) {
return tea.View{
Content: a.minsize.View(a.width, a.height),
AltScreen: true,
WindowTitle: "Default Creds TUI",
}
}
bg := lipgloss.Place(a.width, a.height, lipgloss.Left, lipgloss.Top, padLines(a.core.View(), a.width))
bg = a.modals.Render(bg)
bg = a.notif.Render(bg)
bg = zone.Scan(bg)
return tea.View{
Content: bg,
AltScreen: true,
WindowTitle: "Default Creds TUI",
MouseMode: tea.MouseModeCellMotion,
}
}
func padLines(s string, width int) string {
lines := strings.Split(s, "\n")
for i, l := range lines {
if w := lipgloss.Width(l); w < width {
lines[i] = l + strings.Repeat(" ", width-w)
}
}
return strings.Join(lines, "\n")
}
+89
View File
@@ -0,0 +1,89 @@
package main
import (
"fmt"
"strings"
"charm.land/lipgloss/v2"
"github.com/anotherhadi/ilovetui/style"
"github.com/anotherhadi/default-creds-tui/internal/creds"
)
func runSearchCLI(mode creds.Mode, apiURL, query string, noColors bool) error {
var entries []creds.Entry
switch mode {
case creds.Local:
all, err := creds.LoadLocal(creds.LocalDataDir())
if err != nil {
return fmt.Errorf("local data: %w", err)
}
entries = creds.Filter(all, query)
default:
found, _, err := creds.FetchOnline(apiURL, query, 50)
if err != nil {
return fmt.Errorf("online search: %w", err)
}
entries = found
}
if len(entries) == 0 {
fmt.Printf("No results for %q.\n", query)
return nil
}
blocks := make([]string, len(entries))
for i, e := range entries {
blocks[i] = renderEntryCLI(e, noColors)
}
fmt.Println(strings.Join(blocks, "\n\n"))
return nil
}
func renderEntryCLI(e creds.Entry, noColors bool) string {
manufacturerStyle := lipgloss.NewStyle().Bold(true)
nameStyle := lipgloss.NewStyle().Bold(true)
versionStyle := lipgloss.NewStyle()
credStyle := lipgloss.NewStyle()
commentStyle := lipgloss.NewStyle().Italic(true)
tagStyle := lipgloss.NewStyle()
if !noColors {
manufacturerStyle = manufacturerStyle.Foreground(style.S.Subtle)
nameStyle = nameStyle.Foreground(style.S.Primary)
versionStyle = versionStyle.Foreground(style.S.Subtle)
credStyle = credStyle.Foreground(style.S.Success)
commentStyle = commentStyle.Foreground(style.S.Muted)
tagStyle = tagStyle.Foreground(style.S.Subtle)
}
titleParts := []string{manufacturerStyle.Render(strings.ToUpper(e.Manufacturer))}
if e.Version != "" && e.Version != "all" {
titleParts = append(titleParts, versionStyle.Render("v"+e.Version))
}
lines := []string{
strings.Join(titleParts, " · "),
nameStyle.Render(e.Name),
}
var credLines []string
if e.User != "" {
credLines = append(credLines, credStyle.Render("user: "+e.User))
}
if e.Pass != "" {
credLines = append(credLines, credStyle.Render("pass: "+e.Pass))
}
if len(credLines) > 0 {
lines = append(lines, strings.Join(credLines, " "))
}
if e.Comment != "" {
lines = append(lines, commentStyle.Render(e.Comment))
} else if len(e.Tags) > 0 {
lines = append(lines, tagStyle.Render("#"+strings.Join(e.Tags, " #")))
}
return strings.Join(lines, "\n")
}
+114
View File
@@ -0,0 +1,114 @@
package main
import (
"fmt"
"os"
"path/filepath"
"runtime/debug"
tea "charm.land/bubbletea/v2"
zone "github.com/lrstanley/bubblezone/v2"
"github.com/anotherhadi/ilovetui/style"
"github.com/spf13/cobra"
"github.com/anotherhadi/default-creds-tui/internal/config"
"github.com/anotherhadi/default-creds-tui/internal/creds"
)
var version = "dev"
func main() {
var (
flagConfig string
flagAddDefaultConfig bool
flagMode string
flagSearch string
flagNoColors bool
flagPrint bool
)
rootCmd := &cobra.Command{
Use: "default-creds-tui",
Short: "Search factory-set default credentials, without leaving the terminal.",
Version: version,
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
home, _ := os.UserHomeDir()
cfgPath := filepath.Join(home, ".config", "default-creds-tui", "config.yaml")
if flagConfig != "" {
cfgPath = flagConfig
}
if flagAddDefaultConfig {
if err := config.WriteDefaultConfig(cfgPath); err != nil {
return fmt.Errorf("add-default-config: %w", err)
}
fmt.Printf("default config written to %s\n", cfgPath)
return nil
}
if err := config.Load(cfgPath); err != nil {
return fmt.Errorf("config: %w", err)
}
config.Global.Version = version
modeStr := config.Global.App.Mode
if flagMode != "" {
modeStr = flagMode
}
mode, err := creds.ParseMode(modeStr)
if err != nil {
return fmt.Errorf("mode: %w", err)
}
mode = creds.Resolve(mode)
style.Init()
if flagSearch != "" {
return runSearchCLI(mode, config.Global.App.ApiURL, flagSearch, flagNoColors)
}
zone.NewGlobal()
printOnEnter := config.Global.App.PrintOnEnter || flagPrint
p := tea.NewProgram(newApp(mode, config.Global.App.ApiURL, printOnEnter))
finalModel, err := p.Run()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if am, ok := finalModel.(appModel); ok {
if e, ok := am.core.PrintEntry(); ok {
fmt.Println(renderEntryCLI(e, flagNoColors))
}
}
return nil
},
}
rootCmd.Flags().StringVarP(&flagConfig, "config", "c", "", "path to config file")
rootCmd.Flags().BoolVar(&flagAddDefaultConfig, "add-default-config", false, "copy the default config file to the config path and exit")
rootCmd.Flags().StringVarP(&flagMode, "mode", "m", "", "credential source: auto, online or local")
rootCmd.Flags().StringVarP(&flagSearch, "search", "s", "", "search from the CLI and print results directly, without launching the TUI")
rootCmd.Flags().BoolVar(&flagNoColors, "no-colors", false, "disable colored output (used with --search)")
rootCmd.Flags().BoolVar(&flagPrint, "print", false, "pressing enter prints the selected result to stdout and exits, instead of copying the password")
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func init() {
if version != "dev" {
return
}
if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "" && info.Main.Version != "(devel)" {
version = info.Main.Version
}
}