| |
| |
| |
| |
| |
| |
|
|
| import type { ThemeMode } from '@automaker/types'; |
|
|
| |
| |
| |
| export interface TerminalConfig { |
| enabled: boolean; |
| customPrompt: boolean; |
| promptFormat: 'standard' | 'minimal' | 'powerline' | 'starship'; |
| showGitBranch: boolean; |
| showGitStatus: boolean; |
| showUserHost: boolean; |
| showPath: boolean; |
| pathStyle: 'full' | 'short' | 'basename'; |
| pathDepth: number; |
| showTime: boolean; |
| showExitStatus: boolean; |
| customAliases: string; |
| customEnvVars: Record<string, string>; |
| rcFileVersion?: number; |
| } |
|
|
| |
| |
| |
| export interface TerminalTheme { |
| background: string; |
| foreground: string; |
| cursor: string; |
| cursorAccent: string; |
| selectionBackground: string; |
| selectionForeground?: string; |
| black: string; |
| red: string; |
| green: string; |
| yellow: string; |
| blue: string; |
| magenta: string; |
| cyan: string; |
| white: string; |
| brightBlack: string; |
| brightRed: string; |
| brightGreen: string; |
| brightYellow: string; |
| brightBlue: string; |
| brightMagenta: string; |
| brightCyan: string; |
| brightWhite: string; |
| } |
|
|
| |
| |
| |
| export interface ANSIColors { |
| user: string; |
| host: string; |
| path: string; |
| gitBranch: string; |
| gitDirty: string; |
| prompt: string; |
| reset: string; |
| } |
|
|
| const STARTUP_COLOR_PRIMARY = 51; |
| const STARTUP_COLOR_SECONDARY = 39; |
| const STARTUP_COLOR_ACCENT = 33; |
| const DEFAULT_PATH_DEPTH = 0; |
| const DEFAULT_PATH_STYLE: TerminalConfig['pathStyle'] = 'full'; |
| const OMP_THEME_ENV_VAR = 'AUTOMAKER_OMP_THEME'; |
| const OMP_BINARY = 'oh-my-posh'; |
| const OMP_SHELL_BASH = 'bash'; |
| const OMP_SHELL_ZSH = 'zsh'; |
|
|
| |
| |
| |
| function hexToRgb(hex: string): { r: number; g: number; b: number } { |
| const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); |
| if (!result) { |
| throw new Error(`Invalid hex color: ${hex}`); |
| } |
| return { |
| r: parseInt(result[1], 16), |
| g: parseInt(result[2], 16), |
| b: parseInt(result[3], 16), |
| }; |
| } |
|
|
| |
| |
| |
| function colorDistance( |
| c1: { r: number; g: number; b: number }, |
| c2: { r: number; g: number; b: number } |
| ): number { |
| return Math.sqrt(Math.pow(c1.r - c2.r, 2) + Math.pow(c1.g - c2.g, 2) + Math.pow(c1.b - c2.b, 2)); |
| } |
|
|
| |
| |
| |
| const XTERM_256_PALETTE: Array<{ r: number; g: number; b: number }> = []; |
|
|
| |
| |
| const levels = [0, 95, 135, 175, 215, 255]; |
| for (let r = 0; r < 6; r++) { |
| for (let g = 0; g < 6; g++) { |
| for (let b = 0; b < 6; b++) { |
| XTERM_256_PALETTE.push({ r: levels[r], g: levels[g], b: levels[b] }); |
| } |
| } |
| } |
|
|
| |
| for (let i = 0; i < 24; i++) { |
| const gray = 8 + i * 10; |
| XTERM_256_PALETTE.push({ r: gray, g: gray, b: gray }); |
| } |
|
|
| |
| |
| |
| export function hexToXterm256(hex: string): number { |
| const rgb = hexToRgb(hex); |
| let closestIndex = 16; |
| let minDistance = Infinity; |
|
|
| XTERM_256_PALETTE.forEach((color, index) => { |
| const distance = colorDistance(rgb, color); |
| if (distance < minDistance) { |
| minDistance = distance; |
| closestIndex = index + 16; |
| } |
| }); |
|
|
| return closestIndex; |
| } |
|
|
| |
| |
| |
| export function getThemeANSIColors(theme: TerminalTheme): ANSIColors { |
| return { |
| user: `\\[\\e[38;5;${hexToXterm256(theme.cyan)}m\\]`, |
| host: `\\[\\e[38;5;${hexToXterm256(theme.blue)}m\\]`, |
| path: `\\[\\e[38;5;${hexToXterm256(theme.yellow)}m\\]`, |
| gitBranch: `\\[\\e[38;5;${hexToXterm256(theme.magenta)}m\\]`, |
| gitDirty: `\\[\\e[38;5;${hexToXterm256(theme.red)}m\\]`, |
| prompt: `\\[\\e[38;5;${hexToXterm256(theme.green)}m\\]`, |
| reset: '\\[\\e[0m\\]', |
| }; |
| } |
|
|
| |
| |
| |
| function shellEscape(str: string): string { |
| return str.replace(/([`$\\"])/g, '\\$1'); |
| } |
|
|
| |
| |
| |
| function isValidEnvVarName(name: string): boolean { |
| return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name); |
| } |
|
|
| function stripPromptEscapes(ansiColor: string): string { |
| return ansiColor.replace(/\\\[/g, '').replace(/\\\]/g, ''); |
| } |
|
|
| function normalizePathStyle( |
| pathStyle: TerminalConfig['pathStyle'] | undefined |
| ): TerminalConfig['pathStyle'] { |
| if (pathStyle === 'short' || pathStyle === 'basename') { |
| return pathStyle; |
| } |
| return DEFAULT_PATH_STYLE; |
| } |
|
|
| function normalizePathDepth(pathDepth: number | undefined): number { |
| const depth = |
| typeof pathDepth === 'number' && Number.isFinite(pathDepth) ? pathDepth : DEFAULT_PATH_DEPTH; |
| return Math.max(DEFAULT_PATH_DEPTH, Math.floor(depth)); |
| } |
|
|
| function generateOhMyPoshInit( |
| shell: typeof OMP_SHELL_BASH | typeof OMP_SHELL_ZSH, |
| fallback: string |
| ) { |
| const themeVar = `$${OMP_THEME_ENV_VAR}`; |
| const initCommand = `${OMP_BINARY} init ${shell} --config`; |
| return `if [ -n "${themeVar}" ] && command -v ${OMP_BINARY} >/dev/null 2>&1; then |
| automaker_omp_theme="$(automaker_resolve_omp_theme)" |
| if [ -n "$automaker_omp_theme" ]; then |
| eval "$(${initCommand} "$automaker_omp_theme")" |
| else |
| ${fallback} |
| fi |
| else |
| ${fallback} |
| fi`; |
| } |
|
|
| |
| |
| |
| export function generateCommonFunctions(config: TerminalConfig): string { |
| const gitPrompt = config.showGitBranch |
| ? ` |
| automaker_git_prompt() { |
| local branch="" |
| local dirty="" |
| |
| # Check if we're in a git repository |
| if git rev-parse --git-dir > /dev/null 2>&1; then |
| # Get current branch name |
| branch=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null) |
| |
| ${ |
| config.showGitStatus |
| ? ` |
| # Check if working directory is dirty |
| if [ -n "$(git status --porcelain 2>/dev/null)" ]; then |
| dirty="*" |
| fi |
| ` |
| : '' |
| } |
| |
| if [ -n "$branch" ]; then |
| echo -n " ($branch$dirty)" |
| fi |
| fi |
| } |
| ` |
| : ` |
| automaker_git_prompt() { |
| # Git prompt disabled |
| echo -n "" |
| } |
| `; |
|
|
| return `#!/bin/sh |
| # Automaker Terminal Configuration - Common Functions v1.0 |
| |
| ${gitPrompt} |
| |
| AUTOMAKER_INFO_UNKNOWN="Unknown" |
| AUTOMAKER_BANNER_LABEL_WIDTH=12 |
| AUTOMAKER_BYTES_PER_KIB=1024 |
| AUTOMAKER_KIB_PER_MIB=1024 |
| AUTOMAKER_MIB_PER_GIB=1024 |
| AUTOMAKER_COLOR_PRIMARY="\\033[38;5;${STARTUP_COLOR_PRIMARY}m" |
| AUTOMAKER_COLOR_SECONDARY="\\033[38;5;${STARTUP_COLOR_SECONDARY}m" |
| AUTOMAKER_COLOR_ACCENT="\\033[38;5;${STARTUP_COLOR_ACCENT}m" |
| AUTOMAKER_COLOR_RESET="\\033[0m" |
| AUTOMAKER_SHOW_TIME="${config.showTime === true ? 'true' : 'false'}" |
| AUTOMAKER_SHOW_EXIT_STATUS="${config.showExitStatus === true ? 'true' : 'false'}" |
| AUTOMAKER_SHOW_USER_HOST="${config.showUserHost === false ? 'false' : 'true'}" |
| AUTOMAKER_SHOW_PATH="${config.showPath === false ? 'false' : 'true'}" |
| AUTOMAKER_PATH_STYLE="${normalizePathStyle(config.pathStyle)}" |
| AUTOMAKER_PATH_DEPTH=${normalizePathDepth(config.pathDepth)} |
| automaker_default_themes_dir="\${XDG_DATA_HOME:-\$HOME/.local/share}/oh-my-posh/themes" |
| if [ -z "$POSH_THEMES_PATH" ] || [ ! -d "$POSH_THEMES_PATH" ]; then |
| POSH_THEMES_PATH="$automaker_default_themes_dir" |
| fi |
| export POSH_THEMES_PATH |
| |
| automaker_resolve_omp_theme() { |
| automaker_theme_name="$AUTOMAKER_OMP_THEME" |
| if [ -z "$automaker_theme_name" ]; then |
| return 1 |
| fi |
| |
| if [ -f "$automaker_theme_name" ]; then |
| printf '%s' "$automaker_theme_name" |
| return 0 |
| fi |
| |
| automaker_themes_base="\${POSH_THEMES_PATH%/}" |
| if [ -n "$automaker_themes_base" ]; then |
| if [ -f "$automaker_themes_base/$automaker_theme_name.omp.json" ]; then |
| printf '%s' "$automaker_themes_base/$automaker_theme_name.omp.json" |
| return 0 |
| fi |
| if [ -f "$automaker_themes_base/$automaker_theme_name.omp.yaml" ]; then |
| printf '%s' "$automaker_themes_base/$automaker_theme_name.omp.yaml" |
| return 0 |
| fi |
| fi |
| |
| return 1 |
| } |
| |
| automaker_command_exists() { |
| command -v "$1" >/dev/null 2>&1 |
| } |
| |
| automaker_get_os() { |
| if [ -f /etc/os-release ]; then |
| . /etc/os-release |
| if [ -n "$PRETTY_NAME" ]; then |
| echo "$PRETTY_NAME" |
| return |
| fi |
| if [ -n "$NAME" ] && [ -n "$VERSION" ]; then |
| echo "$NAME $VERSION" |
| return |
| fi |
| fi |
| |
| if automaker_command_exists sw_vers; then |
| echo "$(sw_vers -productName) $(sw_vers -productVersion)" |
| return |
| fi |
| |
| uname -s 2>/dev/null || echo "$AUTOMAKER_INFO_UNKNOWN" |
| } |
| |
| automaker_get_uptime() { |
| if automaker_command_exists uptime; then |
| if uptime -p >/dev/null 2>&1; then |
| uptime -p |
| return |
| fi |
| uptime 2>/dev/null | sed 's/.*up \\([^,]*\\).*/\\1/' || uptime 2>/dev/null |
| return |
| fi |
| |
| echo "$AUTOMAKER_INFO_UNKNOWN" |
| } |
| |
| automaker_get_cpu() { |
| if automaker_command_exists lscpu; then |
| lscpu | sed -n 's/Model name:[[:space:]]*//p' | head -n 1 |
| return |
| fi |
| |
| if automaker_command_exists sysctl; then |
| sysctl -n machdep.cpu.brand_string 2>/dev/null || sysctl -n hw.model 2>/dev/null |
| return |
| fi |
| |
| uname -m 2>/dev/null || echo "$AUTOMAKER_INFO_UNKNOWN" |
| } |
| |
| automaker_get_memory() { |
| if automaker_command_exists free; then |
| free -h | awk '/Mem:/ {print $3 " / " $2}' |
| return |
| fi |
| |
| if automaker_command_exists vm_stat; then |
| local page_size |
| local pages_free |
| local pages_active |
| local pages_inactive |
| local pages_wired |
| local pages_total |
| page_size=$(vm_stat | awk '/page size of/ {print $8}') |
| pages_free=$(vm_stat | awk '/Pages free/ {print $3}' | tr -d '.') |
| pages_active=$(vm_stat | awk '/Pages active/ {print $3}' | tr -d '.') |
| pages_inactive=$(vm_stat | awk '/Pages inactive/ {print $3}' | tr -d '.') |
| pages_wired=$(vm_stat | awk '/Pages wired down/ {print $4}' | tr -d '.') |
| pages_total=$((pages_free + pages_active + pages_inactive + pages_wired)) |
| awk -v total="$pages_total" -v free="$pages_free" -v size="$page_size" \ |
| -v bytes_kib="$AUTOMAKER_BYTES_PER_KIB" \ |
| -v kib_mib="$AUTOMAKER_KIB_PER_MIB" \ |
| -v mib_gib="$AUTOMAKER_MIB_PER_GIB" \ |
| 'BEGIN { |
| total_gb = total * size / bytes_kib / kib_mib / mib_gib; |
| used_gb = (total - free) * size / bytes_kib / kib_mib / mib_gib; |
| printf("%.1f GB / %.1f GB", used_gb, total_gb); |
| }' |
| return |
| fi |
| |
| if automaker_command_exists sysctl; then |
| local total_bytes |
| total_bytes=$(sysctl -n hw.memsize 2>/dev/null) |
| if [ -n "$total_bytes" ]; then |
| awk -v total="$total_bytes" \ |
| -v bytes_kib="$AUTOMAKER_BYTES_PER_KIB" \ |
| -v kib_mib="$AUTOMAKER_KIB_PER_MIB" \ |
| -v mib_gib="$AUTOMAKER_MIB_PER_GIB" \ |
| 'BEGIN {printf("%.1f GB", total / bytes_kib / kib_mib / mib_gib)}' |
| return |
| fi |
| fi |
| |
| echo "$AUTOMAKER_INFO_UNKNOWN" |
| } |
| |
| automaker_get_disk() { |
| if automaker_command_exists df; then |
| df -h / 2>/dev/null | awk 'NR==2 {print $3 " / " $2}' |
| return |
| fi |
| |
| echo "$AUTOMAKER_INFO_UNKNOWN" |
| } |
| |
| automaker_get_ip() { |
| if automaker_command_exists hostname; then |
| local ip_addr |
| ip_addr=$(hostname -I 2>/dev/null | awk '{print $1}') |
| if [ -n "$ip_addr" ]; then |
| echo "$ip_addr" |
| return |
| fi |
| fi |
| |
| if automaker_command_exists ipconfig; then |
| local ip_addr |
| ip_addr=$(ipconfig getifaddr en0 2>/dev/null) |
| if [ -n "$ip_addr" ]; then |
| echo "$ip_addr" |
| return |
| fi |
| fi |
| |
| echo "$AUTOMAKER_INFO_UNKNOWN" |
| } |
| |
| automaker_trim_path_depth() { |
| local path="$1" |
| local depth="$2" |
| if [ -z "$depth" ] || [ "$depth" -le 0 ]; then |
| echo "$path" |
| return |
| fi |
| |
| echo "$path" | awk -v depth="$depth" -F/ '{ |
| prefix="" |
| start=1 |
| if ($1=="") { prefix="/"; start=2 } |
| else if ($1=="~") { prefix="~/"; start=2 } |
| n=NF |
| if (n < start) { |
| if (prefix=="/") { print "/" } |
| else if (prefix=="~/") { print "~" } |
| else { print $0 } |
| next |
| } |
| segCount = n - start + 1 |
| d = depth |
| if (d > segCount) { d = segCount } |
| out="" |
| for (i = n - d + 1; i <= n; i++) { |
| out = out (out=="" ? "" : "/") $i |
| } |
| if (prefix=="/") { |
| if (out=="") { out="/" } else { out="/" out } |
| } else if (prefix=="~/") { |
| if (out=="") { out="~" } else { out="~/" out } |
| } |
| print out |
| }' |
| } |
| |
| automaker_shorten_path() { |
| local path="$1" |
| echo "$path" | awk -F/ '{ |
| prefix="" |
| start=1 |
| if ($1=="") { prefix="/"; start=2 } |
| else if ($1=="~") { prefix="~/"; start=2 } |
| n=NF |
| if (n < start) { |
| if (prefix=="/") { print "/" } |
| else if (prefix=="~/") { print "~" } |
| else { print $0 } |
| next |
| } |
| out="" |
| for (i = start; i <= n; i++) { |
| seg = $i |
| if (i < n && length(seg) > 0) { seg = substr(seg, 1, 1) } |
| out = out (out=="" ? "" : "/") seg |
| } |
| if (prefix=="/") { out="/" out } |
| else if (prefix=="~/") { out="~/" out } |
| print out |
| }' |
| } |
| |
| automaker_prompt_path() { |
| if [ "$AUTOMAKER_SHOW_PATH" != "true" ]; then |
| return |
| fi |
| |
| local current_path="$PWD" |
| if [ -n "$HOME" ] && [ "\${current_path#"$HOME"}" != "$current_path" ]; then |
| current_path="~\${current_path#$HOME}" |
| fi |
| |
| if [ "$AUTOMAKER_PATH_DEPTH" -gt 0 ]; then |
| current_path=$(automaker_trim_path_depth "$current_path" "$AUTOMAKER_PATH_DEPTH") |
| fi |
| |
| case "$AUTOMAKER_PATH_STYLE" in |
| basename) |
| if [ "$current_path" = "/" ] || [ "$current_path" = "~" ]; then |
| echo -n "$current_path" |
| else |
| echo -n "\${current_path##*/}" |
| fi |
| ;; |
| short) |
| echo -n "$(automaker_shorten_path "$current_path")" |
| ;; |
| full|*) |
| echo -n "$current_path" |
| ;; |
| esac |
| } |
| |
| automaker_prompt_time() { |
| if [ "$AUTOMAKER_SHOW_TIME" != "true" ]; then |
| return |
| fi |
| |
| date +%H:%M |
| } |
| |
| automaker_prompt_status() { |
| automaker_last_status=$? |
| if [ "$AUTOMAKER_SHOW_EXIT_STATUS" != "true" ]; then |
| return |
| fi |
| |
| if [ "$automaker_last_status" -eq 0 ]; then |
| return |
| fi |
| |
| printf "✗ %s" "$automaker_last_status" |
| } |
| |
| automaker_show_banner() { |
| local label_width="$AUTOMAKER_BANNER_LABEL_WIDTH" |
| local logo_line_1=" █▀▀█ █ █ ▀▀█▀▀ █▀▀█ █▀▄▀█ █▀▀█ █ █ █▀▀ █▀▀█ " |
| local logo_line_2=" █▄▄█ █ █ █ █ █ █ ▀ █ █▄▄█ █▀▄ █▀▀ █▄▄▀ " |
| local logo_line_3=" ▀ ▀ ▀▀▀ ▀ ▀▀▀▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀▀▀ ▀ ▀▀ " |
| local accent_color="\${AUTOMAKER_COLOR_PRIMARY}" |
| local secondary_color="\${AUTOMAKER_COLOR_SECONDARY}" |
| local tertiary_color="\${AUTOMAKER_COLOR_ACCENT}" |
| local label_color="\${AUTOMAKER_COLOR_SECONDARY}" |
| local reset_color="\${AUTOMAKER_COLOR_RESET}" |
| |
| printf "%b%s%b\n" "$accent_color" "$logo_line_1" "$reset_color" |
| printf "%b%s%b\n" "$secondary_color" "$logo_line_2" "$reset_color" |
| printf "%b%s%b\n" "$tertiary_color" "$logo_line_3" "$reset_color" |
| printf "\n" |
| |
| local shell_name="\${SHELL##*/}" |
| if [ -z "$shell_name" ]; then |
| shell_name=$(basename "$0" 2>/dev/null || echo "shell") |
| fi |
| local user_host="\${USER:-unknown}@$(hostname 2>/dev/null || echo unknown)" |
| printf "%b%s%b\n" "$label_color" "$user_host" "$reset_color" |
| |
| printf "%b%-\${label_width}s%b %s\n" "$label_color" "OS:" "$reset_color" "$(automaker_get_os)" |
| printf "%b%-\${label_width}s%b %s\n" "$label_color" "Uptime:" "$reset_color" "$(automaker_get_uptime)" |
| printf "%b%-\${label_width}s%b %s\n" "$label_color" "Shell:" "$reset_color" "$shell_name" |
| printf "%b%-\${label_width}s%b %s\n" "$label_color" "Terminal:" "$reset_color" "\${TERM_PROGRAM:-$TERM}" |
| printf "%b%-\${label_width}s%b %s\n" "$label_color" "CPU:" "$reset_color" "$(automaker_get_cpu)" |
| printf "%b%-\${label_width}s%b %s\n" "$label_color" "Memory:" "$reset_color" "$(automaker_get_memory)" |
| printf "%b%-\${label_width}s%b %s\n" "$label_color" "Disk:" "$reset_color" "$(automaker_get_disk)" |
| printf "%b%-\${label_width}s%b %s\n" "$label_color" "Local IP:" "$reset_color" "$(automaker_get_ip)" |
| printf "\n" |
| } |
| |
| automaker_show_banner_once() { |
| case "$-" in |
| *i*) ;; |
| *) return ;; |
| esac |
| |
| if [ "$AUTOMAKER_BANNER_SHOWN" = "true" ]; then |
| return |
| fi |
| |
| automaker_show_banner |
| export AUTOMAKER_BANNER_SHOWN="true" |
| } |
| `; |
| } |
|
|
| |
| |
| |
| function generatePrompt( |
| format: TerminalConfig['promptFormat'], |
| colors: ANSIColors, |
| config: TerminalConfig |
| ): string { |
| const userHostSegment = config.showUserHost |
| ? `${colors.user}\\u${colors.reset}@${colors.host}\\h${colors.reset}` |
| : ''; |
| const pathSegment = config.showPath |
| ? `${colors.path}\\$(automaker_prompt_path)${colors.reset}` |
| : ''; |
| const gitSegment = config.showGitBranch |
| ? `${colors.gitBranch}\\$(automaker_git_prompt)${colors.reset}` |
| : ''; |
| const timeSegment = config.showTime |
| ? `${colors.gitBranch}[\\$(automaker_prompt_time)]${colors.reset}` |
| : ''; |
| const statusSegment = config.showExitStatus |
| ? `${colors.gitDirty}\\$(automaker_prompt_status)${colors.reset}` |
| : ''; |
|
|
| switch (format) { |
| case 'minimal': { |
| const minimalSegments = [timeSegment, userHostSegment, pathSegment, gitSegment, statusSegment] |
| .filter((segment) => segment.length > 0) |
| .join(' '); |
| return `PS1="${minimalSegments ? `${minimalSegments} ` : ''}${colors.prompt}\\$${colors.reset} "`; |
| } |
|
|
| case 'powerline': { |
| const powerlineCoreSegments = [ |
| userHostSegment ? `[${userHostSegment}]` : '', |
| pathSegment ? `[${pathSegment}]` : '', |
| ].filter((segment) => segment.length > 0); |
| const powerlineCore = powerlineCoreSegments.join('─'); |
| const powerlineExtras = [gitSegment, timeSegment, statusSegment] |
| .filter((segment) => segment.length > 0) |
| .join(' '); |
| const powerlineLine = [powerlineCore, powerlineExtras] |
| .filter((segment) => segment.length > 0) |
| .join(' '); |
| return `PS1="┌─${powerlineLine}\\n└─${colors.prompt}\\$${colors.reset} "`; |
| } |
|
|
| case 'starship': { |
| let starshipLine = ''; |
| if (userHostSegment && pathSegment) { |
| starshipLine = `${userHostSegment} in ${pathSegment}`; |
| } else { |
| starshipLine = [userHostSegment, pathSegment] |
| .filter((segment) => segment.length > 0) |
| .join(' '); |
| } |
| if (gitSegment) { |
| starshipLine = `${starshipLine}${starshipLine ? ' on ' : ''}${gitSegment}`; |
| } |
| const starshipSegments = [timeSegment, starshipLine, statusSegment] |
| .filter((segment) => segment.length > 0) |
| .join(' '); |
| return `PS1="${starshipSegments}\\n${colors.prompt}❯${colors.reset} "`; |
| } |
|
|
| case 'standard': |
| default: { |
| const standardSegments = [ |
| timeSegment, |
| userHostSegment ? `[${userHostSegment}]` : '', |
| pathSegment, |
| gitSegment, |
| statusSegment, |
| ] |
| .filter((segment) => segment.length > 0) |
| .join(' '); |
| return `PS1="${standardSegments ? `${standardSegments} ` : ''}${colors.prompt}\\$${colors.reset} "`; |
| } |
| } |
| } |
|
|
| |
| |
| |
| function generateZshPrompt( |
| format: TerminalConfig['promptFormat'], |
| colors: ANSIColors, |
| config: TerminalConfig |
| ): string { |
| |
| |
| const zshColors = { |
| user: colors.user |
| .replace(/\\[\[\]\\e]/g, '') |
| .replace(/\\e/g, '%{') |
| .replace(/m\\]/g, 'm%}'), |
| host: colors.host |
| .replace(/\\[\[\]\\e]/g, '') |
| .replace(/\\e/g, '%{') |
| .replace(/m\\]/g, 'm%}'), |
| path: colors.path |
| .replace(/\\[\[\]\\e]/g, '') |
| .replace(/\\e/g, '%{') |
| .replace(/m\\]/g, 'm%}'), |
| gitBranch: colors.gitBranch |
| .replace(/\\[\[\]\\e]/g, '') |
| .replace(/\\e/g, '%{') |
| .replace(/m\\]/g, 'm%}'), |
| gitDirty: colors.gitDirty |
| .replace(/\\[\[\]\\e]/g, '') |
| .replace(/\\e/g, '%{') |
| .replace(/m\\]/g, 'm%}'), |
| prompt: colors.prompt |
| .replace(/\\[\[\]\\e]/g, '') |
| .replace(/\\e/g, '%{') |
| .replace(/m\\]/g, 'm%}'), |
| reset: colors.reset |
| .replace(/\\[\[\]\\e]/g, '') |
| .replace(/\\e/g, '%{') |
| .replace(/m\\]/g, 'm%}'), |
| }; |
|
|
| const userHostSegment = config.showUserHost |
| ? `[${zshColors.user}%n${zshColors.reset}@${zshColors.host}%m${zshColors.reset}]` |
| : ''; |
| const pathSegment = config.showPath |
| ? `${zshColors.path}$(automaker_prompt_path)${zshColors.reset}` |
| : ''; |
| const gitSegment = config.showGitBranch |
| ? `${zshColors.gitBranch}$(automaker_git_prompt)${zshColors.reset}` |
| : ''; |
| const timeSegment = config.showTime |
| ? `${zshColors.gitBranch}[$(automaker_prompt_time)]${zshColors.reset}` |
| : ''; |
| const statusSegment = config.showExitStatus |
| ? `${zshColors.gitDirty}$(automaker_prompt_status)${zshColors.reset}` |
| : ''; |
| const segments = [timeSegment, userHostSegment, pathSegment, gitSegment, statusSegment].filter( |
| (segment) => segment.length > 0 |
| ); |
| const inlineSegments = segments.join(' '); |
| const inlineWithSpace = inlineSegments ? `${inlineSegments} ` : ''; |
|
|
| switch (format) { |
| case 'minimal': { |
| return `PROMPT="${inlineWithSpace}${zshColors.prompt}%#${zshColors.reset} "`; |
| } |
|
|
| case 'powerline': { |
| const powerlineCoreSegments = [ |
| userHostSegment ? `[${userHostSegment}]` : '', |
| pathSegment ? `[${pathSegment}]` : '', |
| ].filter((segment) => segment.length > 0); |
| const powerlineCore = powerlineCoreSegments.join('─'); |
| const powerlineExtras = [gitSegment, timeSegment, statusSegment] |
| .filter((segment) => segment.length > 0) |
| .join(' '); |
| const powerlineLine = [powerlineCore, powerlineExtras] |
| .filter((segment) => segment.length > 0) |
| .join(' '); |
| return `PROMPT="┌─${powerlineLine} |
| └─${zshColors.prompt}%#${zshColors.reset} "`; |
| } |
|
|
| case 'starship': { |
| let starshipLine = ''; |
| if (userHostSegment && pathSegment) { |
| starshipLine = `${userHostSegment} in ${pathSegment}`; |
| } else { |
| starshipLine = [userHostSegment, pathSegment] |
| .filter((segment) => segment.length > 0) |
| .join(' '); |
| } |
| if (gitSegment) { |
| starshipLine = `${starshipLine}${starshipLine ? ' on ' : ''}${gitSegment}`; |
| } |
| const starshipSegments = [timeSegment, starshipLine, statusSegment] |
| .filter((segment) => segment.length > 0) |
| .join(' '); |
| return `PROMPT="${starshipSegments} |
| ${zshColors.prompt}❯${zshColors.reset} "`; |
| } |
|
|
| case 'standard': |
| default: { |
| const standardSegments = [ |
| timeSegment, |
| userHostSegment ? `[${userHostSegment}]` : '', |
| pathSegment, |
| gitSegment, |
| statusSegment, |
| ] |
| .filter((segment) => segment.length > 0) |
| .join(' '); |
| return `PROMPT="${standardSegments ? `${standardSegments} ` : ''}${zshColors.prompt}%#${zshColors.reset} "`; |
| } |
| } |
| } |
|
|
| |
| |
| |
| function generateAliases(config: TerminalConfig): string { |
| if (!config.customAliases) return ''; |
|
|
| |
| const escapedAliases = shellEscape(config.customAliases); |
| return ` |
| # Custom aliases |
| ${escapedAliases} |
| `; |
| } |
|
|
| |
| |
| |
| function generateEnvVars(config: TerminalConfig): string { |
| if (!config.customEnvVars || Object.keys(config.customEnvVars).length === 0) { |
| return ''; |
| } |
|
|
| const validEnvVars = Object.entries(config.customEnvVars) |
| .filter(([name]) => isValidEnvVarName(name)) |
| .map(([name, value]) => `export ${name}="${shellEscape(value)}"`) |
| .join('\n'); |
|
|
| return validEnvVars |
| ? ` |
| # Custom environment variables |
| ${validEnvVars} |
| ` |
| : ''; |
| } |
|
|
| |
| |
| |
| export function generateBashrc(theme: TerminalTheme, config: TerminalConfig): string { |
| const colors = getThemeANSIColors(theme); |
| const promptLine = generatePrompt(config.promptFormat, colors, config); |
| const promptInitializer = generateOhMyPoshInit(OMP_SHELL_BASH, promptLine); |
|
|
| return `#!/bin/bash |
| # Automaker Terminal Configuration v1.0 |
| # This file is automatically generated - manual edits will be overwritten |
| |
| # Source user's original bashrc first (preserves user configuration) |
| if [ -f "$HOME/.bashrc" ]; then |
| source "$HOME/.bashrc" |
| fi |
| |
| # Load Automaker theme colors |
| AUTOMAKER_THEME="\${AUTOMAKER_THEME:-dark}" |
| if [ -f "\${BASH_SOURCE%/*}/themes/$AUTOMAKER_THEME.sh" ]; then |
| source "\${BASH_SOURCE%/*}/themes/$AUTOMAKER_THEME.sh" |
| fi |
| |
| # Load common functions (git prompt) |
| if [ -f "\${BASH_SOURCE%/*}/common.sh" ]; then |
| source "\${BASH_SOURCE%/*}/common.sh" |
| fi |
| |
| # Show Automaker banner on shell start |
| if command -v automaker_show_banner_once >/dev/null 2>&1; then |
| automaker_show_banner_once |
| fi |
| |
| # Set custom prompt (only if enabled) |
| if [ "$AUTOMAKER_CUSTOM_PROMPT" = "true" ]; then |
| ${promptInitializer} |
| fi |
| ${generateAliases(config)}${generateEnvVars(config)} |
| # Load user customizations (if exists) |
| if [ -f "\${BASH_SOURCE%/*}/user-custom.sh" ]; then |
| source "\${BASH_SOURCE%/*}/user-custom.sh" |
| fi |
| `; |
| } |
|
|
| |
| |
| |
| export function generateZshrc(theme: TerminalTheme, config: TerminalConfig): string { |
| const colors = getThemeANSIColors(theme); |
| const promptLine = generateZshPrompt(config.promptFormat, colors, config); |
| const promptInitializer = generateOhMyPoshInit(OMP_SHELL_ZSH, promptLine); |
|
|
| return `#!/bin/zsh |
| # Automaker Terminal Configuration v1.0 |
| # This file is automatically generated - manual edits will be overwritten |
| |
| # Source user's original zshrc first (preserves user configuration) |
| if [ -f "$HOME/.zshrc" ]; then |
| source "$HOME/.zshrc" |
| fi |
| |
| # Load Automaker theme colors |
| AUTOMAKER_THEME="\${AUTOMAKER_THEME:-dark}" |
| if [ -f "\${ZDOTDIR:-\${0:a:h}}/themes/$AUTOMAKER_THEME.sh" ]; then |
| source "\${ZDOTDIR:-\${0:a:h}}/themes/$AUTOMAKER_THEME.sh" |
| fi |
| |
| # Load common functions (git prompt) |
| if [ -f "\${ZDOTDIR:-\${0:a:h}}/common.sh" ]; then |
| source "\${ZDOTDIR:-\${0:a:h}}/common.sh" |
| fi |
| |
| # Enable command substitution in PROMPT |
| setopt PROMPT_SUBST |
| |
| # Show Automaker banner on shell start |
| if command -v automaker_show_banner_once >/dev/null 2>&1; then |
| automaker_show_banner_once |
| fi |
| |
| # Set custom prompt (only if enabled) |
| if [ "$AUTOMAKER_CUSTOM_PROMPT" = "true" ]; then |
| ${promptInitializer} |
| fi |
| ${generateAliases(config)}${generateEnvVars(config)} |
| # Load user customizations (if exists) |
| if [ -f "\${ZDOTDIR:-\${0:a:h}}/user-custom.sh" ]; then |
| source "\${ZDOTDIR:-\${0:a:h}}/user-custom.sh" |
| fi |
| `; |
| } |
|
|
| |
| |
| |
| export function generateThemeColors(theme: TerminalTheme): string { |
| const colors = getThemeANSIColors(theme); |
| const rawColors = { |
| user: stripPromptEscapes(colors.user), |
| host: stripPromptEscapes(colors.host), |
| path: stripPromptEscapes(colors.path), |
| gitBranch: stripPromptEscapes(colors.gitBranch), |
| gitDirty: stripPromptEscapes(colors.gitDirty), |
| prompt: stripPromptEscapes(colors.prompt), |
| reset: stripPromptEscapes(colors.reset), |
| }; |
|
|
| return `#!/bin/sh |
| # Automaker Theme Colors |
| # This file is automatically generated - manual edits will be overwritten |
| |
| # ANSI color codes for prompt |
| export COLOR_USER="${colors.user}" |
| export COLOR_HOST="${colors.host}" |
| export COLOR_PATH="${colors.path}" |
| export COLOR_GIT_BRANCH="${colors.gitBranch}" |
| export COLOR_GIT_DIRTY="${colors.gitDirty}" |
| export COLOR_PROMPT="${colors.prompt}" |
| export COLOR_RESET="${colors.reset}" |
| |
| # ANSI color codes for banner output (no prompt escapes) |
| export COLOR_USER_RAW="${rawColors.user}" |
| export COLOR_HOST_RAW="${rawColors.host}" |
| export COLOR_PATH_RAW="${rawColors.path}" |
| export COLOR_GIT_BRANCH_RAW="${rawColors.gitBranch}" |
| export COLOR_GIT_DIRTY_RAW="${rawColors.gitDirty}" |
| export COLOR_PROMPT_RAW="${rawColors.prompt}" |
| export COLOR_RESET_RAW="${rawColors.reset}" |
| `; |
| } |
|
|
| |
| |
| |
| export function getShellName(rcFile: string): 'bash' | 'zsh' | 'sh' | null { |
| if (rcFile.endsWith('.sh') && rcFile.includes('bashrc')) return 'bash'; |
| if (rcFile.endsWith('.zsh') || rcFile.endsWith('.zshrc')) return 'zsh'; |
| if (rcFile.endsWith('.sh')) return 'sh'; |
| return null; |
| } |
|
|