Add an 80s-style green-on-black bank terminal shell ("banking") using
charmbracelet/bubbletea for full-screen TUI rendering over SSH.
Screens: login, main menu, account summary, account detail with
transactions, wire transfer wizard (6-step form capturing routing
number, destination, beneficiary, amount, memo, auth code), transaction
history with pagination, secure messages with breadcrumb content (fake
internal IPs, vault codes), change PIN, and hidden admin access (99)
that locks after 3 failed attempts with COBOL-style error output.
All key actions (login, navigation, wire transfers, admin attempts) are
logged to the session store. Wire transfer data is the honeypot gold.
Configurable via [shell.banking] in TOML: bank_name, terminal_id, region.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
129 lines
2.8 KiB
Go
129 lines
2.8 KiB
Go
package banking
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
const termWidth = 80
|
|
|
|
// Color palette — green-on-black retro terminal.
|
|
var (
|
|
colorGreen = lipgloss.Color("#00FF00")
|
|
colorDim = lipgloss.Color("#007700")
|
|
colorBlack = lipgloss.Color("#000000")
|
|
colorBright = lipgloss.Color("#AAFFAA")
|
|
colorRed = lipgloss.Color("#FF3333")
|
|
)
|
|
|
|
// Reusable styles.
|
|
var (
|
|
baseStyle = lipgloss.NewStyle().
|
|
Foreground(colorGreen).
|
|
Background(colorBlack)
|
|
|
|
headerStyle = lipgloss.NewStyle().
|
|
Foreground(colorBright).
|
|
Background(colorBlack).
|
|
Bold(true).
|
|
Width(termWidth).
|
|
Align(lipgloss.Center)
|
|
|
|
titleStyle = lipgloss.NewStyle().
|
|
Foreground(colorGreen).
|
|
Background(colorBlack).
|
|
Bold(true)
|
|
|
|
dimStyle = lipgloss.NewStyle().
|
|
Foreground(colorDim).
|
|
Background(colorBlack)
|
|
|
|
errorStyle = lipgloss.NewStyle().
|
|
Foreground(colorRed).
|
|
Background(colorBlack).
|
|
Bold(true)
|
|
|
|
inputStyle = lipgloss.NewStyle().
|
|
Foreground(colorBright).
|
|
Background(colorBlack)
|
|
)
|
|
|
|
// divider returns an 80-column === line.
|
|
func divider() string {
|
|
return dimStyle.Render(strings.Repeat("=", termWidth))
|
|
}
|
|
|
|
// thinDivider returns an 80-column --- line.
|
|
func thinDivider() string {
|
|
return dimStyle.Render(strings.Repeat("-", termWidth))
|
|
}
|
|
|
|
// centerText centers text within 80 columns.
|
|
func centerText(s string) string {
|
|
return headerStyle.Render(s)
|
|
}
|
|
|
|
// padRight pads a string to the given width.
|
|
func padRight(s string, width int) string {
|
|
if len(s) >= width {
|
|
return s[:width]
|
|
}
|
|
return s + strings.Repeat(" ", width-len(s))
|
|
}
|
|
|
|
// formatCurrency formats cents as $X,XXX.XX
|
|
func formatCurrency(cents int64) string {
|
|
negative := cents < 0
|
|
if negative {
|
|
cents = -cents
|
|
}
|
|
dollars := cents / 100
|
|
remainder := cents % 100
|
|
|
|
// Add thousands separators.
|
|
ds := fmt.Sprintf("%d", dollars)
|
|
if len(ds) > 3 {
|
|
var parts []string
|
|
for len(ds) > 3 {
|
|
parts = append([]string{ds[len(ds)-3:]}, parts...)
|
|
ds = ds[:len(ds)-3]
|
|
}
|
|
parts = append([]string{ds}, parts...)
|
|
ds = strings.Join(parts, ",")
|
|
}
|
|
|
|
if negative {
|
|
return fmt.Sprintf("-$%s.%02d", ds, remainder)
|
|
}
|
|
return fmt.Sprintf("$%s.%02d", ds, remainder)
|
|
}
|
|
|
|
// screenFrame wraps content in the persistent header and footer.
|
|
func screenFrame(bankName, terminalID, region, content string) string {
|
|
var b strings.Builder
|
|
|
|
// Header.
|
|
b.WriteString(divider())
|
|
b.WriteString("\n")
|
|
b.WriteString(centerText(bankName + " FEDERAL RESERVE SYSTEM"))
|
|
b.WriteString("\n")
|
|
b.WriteString(centerText("SECURE BANKING TERMINAL"))
|
|
b.WriteString("\n")
|
|
b.WriteString(divider())
|
|
b.WriteString("\n")
|
|
|
|
// Content.
|
|
b.WriteString(content)
|
|
|
|
// Footer.
|
|
b.WriteString("\n")
|
|
b.WriteString(divider())
|
|
b.WriteString("\n")
|
|
footer := fmt.Sprintf(" TERMINAL: %s | REGION: %s | ENCRYPTED SESSION ACTIVE", terminalID, region)
|
|
b.WriteString(dimStyle.Render(padRight(footer, termWidth)))
|
|
|
|
return b.String()
|
|
}
|