Skip to content

Commit 301a783

Browse files
committed
Redesign startup banner with bordered two-column layout
Replace ASCII block-letter banner with a Claude Code Restored-style bordered box featuring skull character art, tips panel, and status info.
1 parent c5415a2 commit 301a783

1 file changed

Lines changed: 101 additions & 32 deletions

File tree

  • rust/crates/rusty-claude-cli/src

rust/crates/rusty-claude-cli/src/main.rs

Lines changed: 101 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3946,6 +3946,14 @@ impl LiveCli {
39463946
}
39473947

39483948
fn startup_banner(&self) -> String {
3949+
let g = "\x1b[38;2;0;255;65m"; // green
3950+
let dg = "\x1b[38;2;0;140;30m"; // dim green
3951+
let b = "\x1b[1m"; // bold
3952+
let d = "\x1b[2m"; // dim
3953+
let r = "\x1b[0m"; // reset
3954+
let sc = "\x1b[38;2;0;255;65m"; // skull color
3955+
let ec = "\x1b[38;2;255;60;60m"; // eye color
3956+
39493957
let cwd = env::current_dir().map_or_else(
39503958
|_| "<unknown>".to_string(),
39513959
|path| path.display().to_string(),
@@ -3954,40 +3962,101 @@ impl LiveCli {
39543962
let git_branch = status
39553963
.as_ref()
39563964
.and_then(|context| context.git_branch.as_deref())
3957-
.unwrap_or("unknown");
3965+
.unwrap_or("");
39583966
let workspace = status.as_ref().map_or_else(
3959-
|| "unknown".to_string(),
3960-
|context| context.git_summary.headline(),
3961-
);
3962-
let session_path = self.session.path.strip_prefix(Path::new(&cwd)).map_or_else(
3963-
|_| self.session.path.display().to_string(),
3964-
|path| path.display().to_string(),
3967+
|| "–".to_string(),
3968+
|context| {
3969+
let h = context.git_summary.headline();
3970+
if h.len() > 20 { format!("{}…", &h[..19]) } else { h }
3971+
},
39653972
);
3966-
format!(
3967-
"\x1b[38;2;0;255;65m\
3968-
██╗ ██╗ █████╗ ██████╗██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗\n\
3969-
██║ ██║██╔══██╗██╔════╝██║ ██╔╝██╔════╝██╔═══██╗██╔══██╗██╔════╝\n\
3970-
███████║███████║██║ █████╔╝ ██║ ██║ ██║██║ ██║█████╗\n\
3971-
██╔══██║██╔══██║██║ ██╔═██╗ ██║ ██║ ██║██║ ██║██╔══╝\n\
3972-
██║ ██║██║ ██║╚██████╗██║ ██╗╚██████╗╚██████╔╝██████╔╝███████╗\n\
3973-
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝\x1b[0m\n\
3974-
\x1b[38;2;0;140;30m >> AI-Powered Hacking Terminal | 100% Local | No Censorship <<\x1b[0m\n\n\
3975-
\x1b[2mModel\x1b[0m {}\n\
3976-
\x1b[2mPermissions\x1b[0m {}\n\
3977-
\x1b[2mBranch\x1b[0m {}\n\
3978-
\x1b[2mWorkspace\x1b[0m {}\n\
3979-
\x1b[2mDirectory\x1b[0m {}\n\
3980-
\x1b[2mSession\x1b[0m {}\n\
3981-
\x1b[2mAuto-save\x1b[0m {}\n\n\
3982-
Type \x1b[1m/help\x1b[0m for commands · \x1b[1m/tools\x1b[0m for security tools · \x1b[2mTab\x1b[0m for completions · \x1b[2mShift+Enter\x1b[0m for newline",
3983-
self.model,
3984-
self.permission_mode.as_str(),
3985-
git_branch,
3986-
workspace,
3987-
cwd,
3988-
self.session.id,
3989-
session_path,
3990-
)
3973+
3974+
// Truncate cwd for display
3975+
let short_cwd = if cwd.len() > 30 {
3976+
let parts: Vec<&str> = cwd.split('/').collect();
3977+
if parts.len() > 3 {
3978+
format!("…/{}/{}", parts[parts.len() - 2], parts[parts.len() - 1])
3979+
} else {
3980+
format!("…{}", &cwd[cwd.len() - 29..])
3981+
}
3982+
} else {
3983+
cwd.clone()
3984+
};
3985+
3986+
let model_short = if self.model.len() > 22 {
3987+
format!("{}…", &self.model[..21])
3988+
} else {
3989+
self.model.clone()
3990+
};
3991+
3992+
let session_short = {
3993+
let s = &self.session.id;
3994+
if s.len() > 16 { format!("{}…", &s[..15]) } else { s.clone() }
3995+
};
3996+
3997+
let perm = self.permission_mode.as_str();
3998+
3999+
// Build banner as a simple two-column box.
4000+
// Left column = 26 chars content, Right column = 28 chars content.
4001+
// Total inner = 26 + 3 (divider) + 28 = 57 chars.
4002+
let w: usize = 59; // total inner width (between │ and │)
4003+
let lw: usize = 27; // left content width
4004+
let rw: usize = 28; // right content width
4005+
4006+
// Helper: pad plain text to width (for non-ANSI content width calculation)
4007+
fn rpad(text: &str, width: usize) -> String {
4008+
if text.len() >= width { text[..width].to_string() }
4009+
else { format!("{}{}", text, " ".repeat(width - text.len())) }
4010+
}
4011+
4012+
// Build each row as: "│ {left_padded} │ {right_padded} │"
4013+
// Colors are injected per-cell, reset at cell boundary.
4014+
let rows: Vec<String> = vec![
4015+
// Row 0: welcome + tips header
4016+
format!("{g}│{r} {b}Ready to hack.{r}{} {dg}│{r} {g}{b}Tips{r}{} {g}│{r}",
4017+
" ".repeat(lw - 16), " ".repeat(rw - 6)),
4018+
// Row 1: blank + /help
4019+
format!("{g}│{r}{} {dg}│{r} {d}/help{r} commands & usage{} {g}│{r}",
4020+
" ".repeat(lw), " ".repeat(rw - 26)),
4021+
// Row 2: skull line 1 + /tools
4022+
format!("{g}│{r} {sc} ▄▄███▄▄ {r}{} {dg}│{r} {d}/tools{r} security toolkit{} {g}│{r}",
4023+
" ".repeat(lw - 17), " ".repeat(rw - 27)),
4024+
// Row 3: skull line 2 (eyes) + Tab
4025+
format!("{g}│{r} {sc}█{ec}▀▀{sc}███{ec}▀▀{sc}█{r}{} {dg}│{r} {d}Tab{r} completions{} {g}│{r}",
4026+
" ".repeat(lw - 17), " ".repeat(rw - 22)),
4027+
// Row 4: skull line 3 + blank
4028+
format!("{g}│{r} {sc}██▄███▄██{r}{} {dg}│{r}{} {g}│{r}",
4029+
" ".repeat(lw - 15), " ".repeat(rw)),
4030+
// Row 5: skull line 4 + status header
4031+
format!("{g}│{r} {sc}▀█▀▀▀█▀ {r}{} {dg}│{r} {g}{b}Status{r}{} {g}│{r}",
4032+
" ".repeat(lw - 17), " ".repeat(rw - 8)),
4033+
// Row 6: blank + branch
4034+
format!("{g}│{r}{} {dg}│{r} {d}Branch{r} {} {g}│{r}",
4035+
" ".repeat(lw), rpad(git_branch, rw - 12)),
4036+
// Row 7: model + workspace
4037+
format!("{g}│{r} {d}Model{r} {} {dg}│{r} {d}Workspace{r} {} {g}│{r}",
4038+
rpad(&model_short, lw - 9), rpad(&workspace, rw - 12)),
4039+
// Row 8: perms + session
4040+
format!("{g}│{r} {d}Perms{r} {} {dg}│{r} {d}Session{r} {} {g}│{r}",
4041+
rpad(perm, lw - 9), rpad(&session_short, rw - 12)),
4042+
// Row 9: dir + blank
4043+
format!("{g}│{r} {d}Dir{r} {} {dg}│{r}{} {g}│{r}",
4044+
rpad(&short_cwd, lw - 9), " ".repeat(rw)),
4045+
];
4046+
4047+
let title = format!(" HackCode v{} ", env!("CARGO_PKG_VERSION"));
4048+
let fill = w.saturating_sub(title.len() + 2);
4049+
let top = format!("{g}╭─{b}{title}{r}{g}{}╮{r}", "─".repeat(fill));
4050+
let bottom = format!("{g}╰{}╯{r}", "─".repeat(w));
4051+
4052+
let mut out = format!("{top}\n");
4053+
for row in &rows {
4054+
out.push_str(row);
4055+
out.push('\n');
4056+
}
4057+
out.push_str(&bottom);
4058+
out.push_str(&format!("\n{dg} >> 100% Local · No Cloud · No Censorship <<{r}"));
4059+
out
39914060
}
39924061

39934062
fn repl_completion_candidates(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {

0 commit comments

Comments
 (0)