Skip to content

Commit 1124baf

Browse files
committed
feat: auto-detect piped output for JSON mode and add semantic exit codes
1 parent f4921d5 commit 1124baf

File tree

11 files changed

+295
-77
lines changed

11 files changed

+295
-77
lines changed

src/commands/browser/start.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use clap::Parser;
22
use serde_json::json;
33

44
use crate::browser::daemon::client::DaemonClient;
5+
use crate::status;
56
use crate::browser::daemon::process;
67
use crate::browser::daemon::protocol::{DaemonCommand, DaemonCreateParams, SessionInfo};
78
use crate::browser::lifecycle::sanitize_connect_url;
@@ -77,7 +78,7 @@ pub async fn run(args: Args, session: Option<&str>) -> anyhow::Result<()> {
7778
// `start` always creates a fresh session — use `steel browser sessions`
7879
// to inspect existing ones.
7980
if DaemonClient::connect(&session_name).await.is_ok() {
80-
eprintln!("Replacing existing session \"{session_name}\"...");
81+
status!("Replacing existing session \"{session_name}\"...");
8182
process::stop_daemon(&session_name).await?;
8283
} else {
8384
// No live daemon, but stale files may remain

src/commands/cache.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use clap::Parser;
22

3+
use crate::status;
4+
35
#[derive(Parser)]
46
pub struct Args {
57
/// Remove all cached files and directories
@@ -13,13 +15,13 @@ pub async fn run(args: Args) -> anyhow::Result<()> {
1315
.join("steel");
1416

1517
if !args.clean {
16-
println!("Steel CLI cache directory: {}", cache_dir.display());
17-
println!("Use --clean to remove all cached files.");
18+
status!("Steel CLI cache directory: {}", cache_dir.display());
19+
status!("Use --clean to remove all cached files.");
1820
return Ok(());
1921
}
2022

2123
if !cache_dir.exists() {
22-
println!("Cache directory does not exist. Nothing to clean.");
24+
status!("Cache directory does not exist. Nothing to clean.");
2325
return Ok(());
2426
}
2527

@@ -35,8 +37,8 @@ pub async fn run(args: Args) -> anyhow::Result<()> {
3537
count += 1;
3638
}
3739

38-
println!("Removed {count} item(s) from cache.");
39-
println!("Cache directory: {}", cache_dir.display());
40+
status!("Removed {count} item(s) from cache.");
41+
status!("Cache directory: {}", cache_dir.display());
4042

4143
Ok(())
4244
}

src/commands/dev.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::process::Command as ProcessCommand;
33
use clap::{Parser, Subcommand};
44

55
use crate::config;
6+
use crate::status;
67

78
#[derive(Subcommand)]
89
pub enum Command {
@@ -66,9 +67,9 @@ fn run_install(args: InstallArgs) -> anyhow::Result<()> {
6667
let path = repo_path();
6768

6869
if path.exists() {
69-
println!("Local Steel Browser runtime already installed.");
70-
println!("repo_path: {}", path.display());
71-
println!("repo_url: {repo_url}");
70+
status!("Local Steel Browser runtime already installed.");
71+
status!("repo_path: {}", path.display());
72+
status!("repo_url: {repo_url}");
7273
return Ok(());
7374
}
7475

@@ -85,17 +86,17 @@ fn run_install(args: InstallArgs) -> anyhow::Result<()> {
8586
anyhow::bail!("Failed to clone repository.");
8687
}
8788

88-
println!("Local Steel Browser runtime installed.");
89-
println!("repo_path: {}", path.display());
90-
println!("repo_url: {repo_url}");
89+
status!("Local Steel Browser runtime installed.");
90+
status!("repo_path: {}", path.display());
91+
status!("repo_url: {repo_url}");
9192

9293
Ok(())
9394
}
9495

9596
fn run_start(args: StartArgs) -> anyhow::Result<()> {
9697
if args.docker_check {
9798
return if is_docker_running() {
98-
println!("Docker is running.");
99+
status!("Docker is running.");
99100
Ok(())
100101
} else {
101102
anyhow::bail!("Docker is not running.");
@@ -124,9 +125,9 @@ fn run_start(args: StartArgs) -> anyhow::Result<()> {
124125
anyhow::bail!("Failed to start local Steel Browser runtime.");
125126
}
126127

127-
println!("Local Steel Browser runtime started.");
128-
println!("repo_path: {}", path.display());
129-
println!("api_port: {port}");
128+
status!("Local Steel Browser runtime started.");
129+
status!("repo_path: {}", path.display());
130+
status!("api_port: {port}");
130131

131132
Ok(())
132133
}
@@ -150,8 +151,8 @@ fn run_stop(args: StopArgs) -> anyhow::Result<()> {
150151
anyhow::bail!("Failed to stop local Steel Browser runtime.");
151152
}
152153

153-
println!("Local Steel Browser runtime stopped.");
154-
println!("repo_path: {}", path.display());
154+
status!("Local Steel Browser runtime stopped.");
155+
status!("repo_path: {}", path.display());
155156

156157
Ok(())
157158
}

src/commands/forge.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use clap::Parser;
44
use dialoguer::{Input, Select};
55
use serde::Deserialize;
66

7+
use crate::status;
8+
79
#[derive(Parser)]
810
pub struct Args {
911
/// Template to start from
@@ -99,7 +101,7 @@ pub async fn run(args: Args) -> anyhow::Result<()> {
99101
manifest.version, template_path
100102
);
101103

102-
println!("Downloading template '{}'...", example.title);
104+
status!("Downloading template '{}'...", example.title);
103105

104106
let client = reqwest::Client::new();
105107
let response = client.get(&download_url).send().await?;
@@ -136,20 +138,20 @@ pub async fn run(args: Args) -> anyhow::Result<()> {
136138
anyhow::bail!("Failed to extract template archive.");
137139
}
138140

139-
println!(
141+
status!(
140142
"Project '{}' created at {}",
141143
project_name,
142144
target_dir.display()
143145
);
144-
println!("\nNext steps:");
145-
println!(" cd {project_name}");
146+
status!("\nNext steps:");
147+
status!(" cd {project_name}");
146148

147149
match example.language.as_deref() {
148150
Some("python") => {
149-
println!(" pip install -r requirements.txt");
151+
status!(" pip install -r requirements.txt");
150152
}
151153
_ => {
152-
println!(" npm install");
154+
status!(" npm install");
153155
}
154156
}
155157

src/commands/login.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use tokio::net::TcpListener;
55

66
use crate::config;
77
use crate::config::auth;
8+
use crate::status;
89
use crate::config::settings::{Config, read_config_from, write_config_to};
910

1011
#[derive(Parser)]
@@ -16,17 +17,17 @@ pub async fn run(_args: Args) -> anyhow::Result<()> {
1617
// Check if already logged in
1718
let existing_auth = auth::resolve_auth();
1819
if existing_auth.api_key.is_some() {
19-
println!("You are already logged in.");
20+
status!("You are already logged in.");
2021
return Ok(());
2122
}
2223

23-
println!("Launching browser for authentication...");
24+
status!("Launching browser for authentication...");
2425

2526
let (api_key, name) = login_flow().await?;
2627

2728
save_api_key(&api_key, &name)?;
2829

29-
println!("Authentication successful! Your API key has been saved.");
30+
status!("Authentication successful! Your API key has been saved.");
3031

3132
Ok(())
3233
}
@@ -45,13 +46,13 @@ async fn login_flow() -> anyhow::Result<(String, String)> {
4546
state
4647
);
4748

48-
println!("Opening your browser for authentication...");
49-
println!("If it does not open automatically, please click:");
50-
println!("{auth_url}");
49+
status!("Opening your browser for authentication...");
50+
status!("If it does not open automatically, please click:");
51+
status!("{auth_url}");
5152

5253
if let Err(e) = open::that(&auth_url) {
53-
eprintln!("Warning: could not open browser automatically: {e}");
54-
eprintln!("Please open the URL above manually.");
54+
status!("Warning: could not open browser automatically: {e}");
55+
status!("Please open the URL above manually.");
5556
}
5657

5758
// Wait for the callback with timeout

src/commands/logout.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use clap::Parser;
22

33
use crate::config;
44
use crate::config::settings::{read_config_from, write_config_to};
5+
use crate::status;
56

67
#[derive(Parser)]
78
pub struct Args {}
@@ -12,13 +13,13 @@ pub async fn run(_args: Args) -> anyhow::Result<()> {
1213
let mut cfg = match read_config_from(&config_path) {
1314
Ok(c) => c,
1415
Err(_) => {
15-
println!("You are not logged in.");
16+
status!("You are not logged in.");
1617
return Ok(());
1718
}
1819
};
1920

2021
if cfg.api_key.is_none() {
21-
println!("You are not logged in.");
22+
status!("You are not logged in.");
2223
return Ok(());
2324
}
2425

@@ -27,7 +28,7 @@ pub async fn run(_args: Args) -> anyhow::Result<()> {
2728

2829
write_config_to(&config_path, &cfg)?;
2930

30-
println!("Successfully logged out. Have a great day!");
31+
status!("Successfully logged out. Have a great day!");
3132

3233
Ok(())
3334
}

src/commands/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ Environment:
179179
STEEL_BROWSER_API_URL Steel Browser API endpoint
180180
STEEL_LOCAL_API_URL Local runtime API endpoint
181181
STEEL_CONFIG_DIR Custom config directory
182+
STEEL_FORCE_TTY Force text output when piped (disable auto-JSON)
183+
NO_COLOR Disable colored output
182184
183185
Examples:
184186
steel scrape https://example.com
@@ -288,7 +290,7 @@ pub enum Command {
288290
}
289291

290292
pub async fn run(cli: Cli) -> anyhow::Result<()> {
291-
crate::util::output::set_json_mode(cli.json);
293+
crate::util::output::init(cli.json);
292294
crate::util::api::init(cli.local, cli.api_url);
293295

294296
match cli.command {

0 commit comments

Comments
 (0)