Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Supported shells: `bash`, `zsh`, `fish`, `powershell`, `elvish`.
| API tools | `scrape`, `screenshot`, `pdf` |
| Local runtime | `dev install`, `dev start`, `dev stop` |
| Credentials | `credentials list`, `credentials create`, `credentials update`, `credentials delete` |
| Account and utility | `login`, `logout`, `config`, `doctor`, `cache`, `update`, `completion` |
| Account and utility | `login`, `logout`, `config`, `settings`, `doctor`, `cache`, `update`, `completion` |

Full flags and schemas: [CLI reference](docs/cli-reference.md).

Expand Down
6 changes: 3 additions & 3 deletions docs/references/steel-browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ Upstream command catalog (pinned reference):

## Modes

- Cloud mode (default): no mode flag required.
- Self-hosted mode: use `--local` or `--api-url <url>`.
- Cloud mode (default): no mode flag or persisted local instance is required.
- Self-hosted mode: use `--local`, `--api-url <url>`, or set `"instance": "local"` in `~/.config/steel/config.json`.

`--api-url` implies self-hosted mode.
`--api-url` implies self-hosted mode. A persisted local instance uses the configured self-hosted endpoint or falls back to `http://localhost:3000/v1`.

## Endpoint Resolution

Expand Down
22 changes: 13 additions & 9 deletions docs/references/steel-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,21 @@ For generated flags and argument schemas, use [../cli-reference.md](../cli-refer
- Browser session state: `~/.config/steel/browser-session-state.json`
- Profile metadata: `~/.config/steel/profiles/<name>.json`

On the first run where telemetry is enabled, the CLI prints a one-time notice to stderr describing what is collected and how to opt out. The notice is suppressed in JSON/non-TTY output.
## Main Config Values

`~/.config/steel/config.json` supports these persisted values:

Telemetry can be disabled persistently in `config.json` with:
| Key | Purpose |
| --- | --- |
| `apiKey` | Cloud API key saved by `steel login`. |
| `name` | Saved account or CLI name associated with login. |
| `instance` | Default API mode: `"cloud"` or `"local"`. `steel settings` updates this value. |
| `browser.apiUrl` | Self-hosted Steel API endpoint used in Local mode. |
| `telemetry.disabled` | Set to `true` to disable telemetry. |

```json
{
"telemetry": {
"disabled": true
}
}
```
Local mode falls back to `http://localhost:3000/v1` when `browser.apiUrl` and the local endpoint environment variables are unset. Explicit `--local` and `--api-url` flags continue to select self-hosted mode for an invocation.

On the first run where telemetry is enabled, the CLI prints a one-time notice to stderr describing what is collected and how to opt out. The notice is suppressed in JSON/non-TTY output.

## Environment Variables (Common)

Expand Down
13 changes: 13 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod profile;
pub mod scrape;
pub mod screenshot;
pub mod sessions;
pub mod settings;
pub mod skills;
pub mod update;

Expand Down Expand Up @@ -195,6 +196,7 @@ Other:
steel login Login to Steel (alias: auth)
steel logout Logout from Steel
steel config Show current configuration
steel settings Change persisted CLI settings
steel doctor Check environment, auth, and connectivity
--preflight Only check auth and API (fast, for agents)
steel forge [template] [-n <name>] Scaffold a new project from a template
Expand Down Expand Up @@ -318,6 +320,9 @@ pub enum Command {
/// Show current configuration
Config(config::Args),

/// Manage persisted CLI settings
Settings(settings::Args),

/// Update to the latest version
Update(update::Args),

Expand Down Expand Up @@ -360,6 +365,7 @@ fn telemetry_command_path(command: &Command) -> Option<String> {
Command::Dev { command } => Some(format!("dev.{}", command.telemetry_name())),
Command::Forge(_) => Some("forge".to_string()),
Command::Config(_) => Some("config".to_string()),
Command::Settings(_) => Some("settings".to_string()),
Command::Update(_) => Some("update".to_string()),
Command::Cache(_) => Some("cache".to_string()),
Command::Profile { command } => Some(format!("profile.{}", command.telemetry_name())),
Expand Down Expand Up @@ -405,6 +411,7 @@ pub async fn run(cli: Cli) -> anyhow::Result<()> {
Command::Dev { command } => dev::run(command).await,
Command::Forge(args) => forge::run(args).await,
Command::Config(args) => config::run(args).await,
Command::Settings(args) => settings::run(args).await,
Command::Update(args) => update::run(args).await,
Command::Cache(args) => cache::run(args).await,
Command::Profile { command } => profile::run(command).await,
Expand Down Expand Up @@ -453,6 +460,12 @@ mod tests {
out
}

#[test]
fn settings_command_is_registered() {
let cli = Cli::try_parse_from(["steel", "settings"]).unwrap();
assert!(matches!(cli.command, Command::Settings(_)));
}

#[tokio::test]
async fn successful_command_emits_started_and_completed_events() {
let _guard = env_lock().lock().await;
Expand Down
51 changes: 44 additions & 7 deletions src/util/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,33 @@ pub fn init(local: bool, api_url: Option<String>) {
API_URL.get_or_init(|| api_url);
}

fn is_local() -> bool {
LOCAL.load(Ordering::Relaxed)
}

fn api_url() -> Option<&'static str> {
API_URL.get().and_then(|o| o.as_deref())
}

/// Resolve the API mode from global flags.
fn resolve_mode(local: bool, api_url: Option<&str>, configured_instance: Option<&str>) -> ApiMode {
ApiMode::resolve(local || configured_instance == Some("local"), api_url)
}

/// Resolve the API mode from global flags + config.
pub fn mode() -> ApiMode {
ApiMode::resolve(is_local(), api_url())
let config = crate::config::settings::read_config().ok();
resolve_mode(
LOCAL.load(Ordering::Relaxed),
api_url(),
config.as_ref().and_then(|c| c.instance.as_deref()),
)
}

/// Resolve API mode and base URL from global flags + env + config.
pub fn resolve() -> (ApiMode, String) {
let mode = ApiMode::resolve(is_local(), api_url());
let env_vars = EnvVars::from_env();
let config = crate::config::settings::read_config().ok();
let mode = resolve_mode(
LOCAL.load(Ordering::Relaxed),
api_url(),
config.as_ref().and_then(|c| c.instance.as_deref()),
);
let local_config_url = config.as_ref().and_then(|c| c.local_api_url());
let base_url = mode.resolve_base_url(api_url(), &env_vars, local_config_url);
(mode, base_url)
Expand All @@ -47,3 +56,31 @@ pub fn resolve_with_auth() -> (ApiMode, String, Auth) {
let auth = auth::resolve_auth();
(mode, base_url, auth)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn configured_local_instance_selects_local_mode() {
assert_eq!(resolve_mode(false, None, Some("local")), ApiMode::Local);
}

#[test]
fn configured_cloud_instance_keeps_cloud_mode() {
assert_eq!(resolve_mode(false, None, Some("cloud")), ApiMode::Cloud);
}

#[test]
fn explicit_api_url_still_selects_local_mode() {
assert_eq!(
resolve_mode(false, Some("http://steel.example/v1"), Some("cloud")),
ApiMode::Local
);
}

#[test]
fn local_flag_still_selects_local_mode() {
assert_eq!(resolve_mode(true, None, Some("cloud")), ApiMode::Local);
}
}