feat: add polished boot screen on CLI startup (#118)

* feat: add polished boot screen on CLI startup

Replace the minimal one-liner REPL banner with an ANSI-styled status
panel that summarizes the agent's runtime state after initialization:
model, database, tool count, enabled features, active channels, and
the gateway URL. The boot screen is shown only in interactive CLI mode
(skipped for single-message -m mode).

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: address PR review feedback on boot screen

- Stop logging gateway auth token in tracing::info! (security)
- Use info.agent_name instead of hardcoded "IronClaw" in header
- Display embeddings provider in features line: "embeddings (openai)"
- Add Display impl for DatabaseBackend, simplify main.rs match

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Illia Polosukhin
2026-02-17 05:50:57 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 7c553b0973
commit 63302ab406
5 changed files with 287 additions and 9 deletions
+55 -7
View File
@@ -338,7 +338,10 @@ async fn main() -> anyhow::Result<()> {
let repl_channel = if let Some(ref msg) = cli.message {
Some(ReplChannel::with_message(msg.clone()))
} else if config.channels.cli.enabled {
Some(ReplChannel::new())
let repl = ReplChannel::new();
// Suppress the one-liner banner; boot screen will be shown instead.
repl.suppress_banner();
Some(repl)
} else {
None
};
@@ -886,12 +889,14 @@ async fn main() -> anyhow::Result<()> {
// Initialize channel manager
let mut channels = ChannelManager::new();
let mut channel_names: Vec<String> = Vec::new();
if let Some(repl) = repl_channel {
channels.add(Box::new(repl));
if cli.message.is_some() {
tracing::info!("Single message mode");
} else {
channel_names.push("repl".to_string());
tracing::info!("REPL mode enabled");
}
}
@@ -1027,6 +1032,7 @@ async fn main() -> anyhow::Result<()> {
}
}
channel_names.push(channel_name.clone());
channels.add(Box::new(SharedWasmChannel::new(channel_arc)));
}
@@ -1071,6 +1077,7 @@ async fn main() -> anyhow::Result<()> {
.parse()
.expect("HttpConfig host:port must be a valid SocketAddr"),
);
channel_names.push("http".to_string());
channels.add(Box::new(http_channel));
tracing::info!(
"HTTP channel enabled on {}:{}",
@@ -1147,6 +1154,7 @@ async fn main() -> anyhow::Result<()> {
);
// Add web gateway channel if configured
let mut gateway_url: Option<String> = None;
if let Some(ref gw_config) = config.channels.gateway {
let mut gw = GatewayChannel::new(gw_config.clone());
if let Some(ref ws) = workspace {
@@ -1179,21 +1187,29 @@ async fn main() -> anyhow::Result<()> {
}
}
gateway_url = Some(format!(
"http://{}:{}/?token={}",
gw_config.host,
gw_config.port,
gw.auth_token()
));
tracing::info!(
"Web gateway enabled on {}:{}",
gw_config.host,
gw_config.port
);
tracing::info!(
"Web UI: http://{}:{}/?token={}",
gw_config.host,
gw_config.port,
gw.auth_token()
);
tracing::info!("Web UI: http://{}:{}/", gw_config.host, gw_config.port);
channel_names.push("gateway".to_string());
channels.add(Box::new(gw));
}
// Capture boot screen info before moving Arcs into AgentDeps.
let boot_tool_count = tools.count();
let boot_llm_model = llm.model_name().to_string();
let boot_cheap_model = cheap_llm.as_ref().map(|c| c.model_name().to_string());
// Create and run the agent
let deps = AgentDeps {
store: db,
@@ -1217,6 +1233,38 @@ async fn main() -> anyhow::Result<()> {
tracing::info!("Agent initialized, starting main loop...");
// Print boot screen for interactive CLI mode (not single-message mode).
if config.channels.cli.enabled && cli.message.is_none() {
let boot_info = ironclaw::boot_screen::BootInfo {
version: env!("CARGO_PKG_VERSION").to_string(),
agent_name: config.agent.name.clone(),
llm_backend: config.llm.backend.to_string(),
llm_model: boot_llm_model,
cheap_model: boot_cheap_model,
db_backend: if cli.no_db {
"none".to_string()
} else {
config.database.backend.to_string()
},
db_connected: !cli.no_db,
tool_count: boot_tool_count,
gateway_url,
embeddings_enabled: config.embeddings.enabled,
embeddings_provider: if config.embeddings.enabled {
Some(config.embeddings.provider.clone())
} else {
None
},
heartbeat_enabled: config.heartbeat.enabled,
heartbeat_interval_secs: config.heartbeat.interval_secs,
sandbox_enabled: config.sandbox.enabled,
claude_code_enabled: config.claude_code.enabled,
routines_enabled: config.routines.enabled,
channels: channel_names,
};
ironclaw::boot_screen::print_boot_screen(&boot_info);
}
// Run the agent (blocks until shutdown)
agent.run().await?;