feat(setup): quick onboarding, preserve .env vars, clean up boot logging (#821)

* feat(setup): quick onboarding, preserve .env vars, clean up boot logging (#751, #674)

- Add upsert_bootstrap_vars() to preserve user-added .env vars on re-onboarding
- Add --quick mode: auto-defaults DB + security, asks only LLM provider (2 steps)
- Auto-triggered onboarding uses quick mode for near-instant first run
- Fix NEAR AI model fetch to use cloud-api.near.ai when API key is set
- Handle missing WASM tools/channels directories gracefully
- Downgrade all boot/shutdown tracing::info! to debug (boot screen shows user output)

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

* fix(setup): gate env_backend variable behind postgres feature flag [skip-regression-check]

Clippy lint fix — not a behavioral change, just moving a variable declaration
inside the cfg(feature = "postgres") block where it's used.

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

* fix(review): address PR review comments

- WASM loaders: use tokio::fs::metadata, only treat NotFound as empty,
  propagate other IO errors, handle TOCTOU in read_dir
- bootstrap: only ignore NotFound in read_to_string, propagate other errors
- wizard: restore print_info/print_success for migrations in interactive
  mode (gated by !config.quick), keep tracing::debug for diagnostics
- tests: use shared crate::config::helpers::ENV_MUTEX instead of separate
  NEARAI_ENV_MUTEX to prevent cross-test env var races
- README: fix quick mode description to mention model selection, clarify
  auto_setup_database may prompt when DATABASE_URL is set

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

* feat(setup): skip prompts for DATABASE_URL in quick mode [skip-regression-check]

auto_setup_database() now uses DATABASE_URL directly without calling
step_database_postgres() (which prompts for confirmation). Quick mode
should be fully non-interactive when env vars are already set.

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

* fix(cli): update --quick help text to mention model selection [skip-regression-check]

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-03-10 05:02:33 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 94d101924e
commit 3a2989d009
18 changed files with 564 additions and 102 deletions
+14 -14
View File
@@ -117,7 +117,7 @@ pub fn create_llm_provider_with_config(
} else {
"session token"
};
tracing::info!(
tracing::debug!(
model = %config.model,
base_url = %config.base_url,
auth = auth_mode,
@@ -156,7 +156,7 @@ async fn create_bedrock_provider(config: &LlmConfig) -> Result<Arc<dyn LlmProvid
})?;
let provider = bedrock::BedrockProvider::new(br).await?;
tracing::info!(
tracing::debug!(
"Using AWS Bedrock (Converse API, region: {}, model: {})",
br.region,
provider.active_model_name(),
@@ -221,7 +221,7 @@ fn create_openai_compat_from_registry(
let client = client.completions_api();
let model = client.completion_model(&config.model);
tracing::info!(
tracing::debug!(
provider = %config.provider_id,
model = %config.model,
base_url = %config.base_url,
@@ -242,7 +242,7 @@ fn create_anthropic_from_registry(
.as_ref()
.is_some_and(|k| k.expose_secret() == crate::llm::config::OAUTH_PLACEHOLDER);
if config.oauth_token.is_some() && (config.api_key.is_none() || api_key_is_placeholder) {
tracing::info!(
tracing::debug!(
provider = %config.provider_id,
model = %config.model,
base_url = if config.base_url.is_empty() { "default" } else { &config.base_url },
@@ -281,14 +281,14 @@ fn create_anthropic_from_registry(
let model = client.completion_model(&config.model);
if cache_retention != CacheRetention::None {
tracing::info!(
tracing::debug!(
model = %config.model,
retention = %cache_retention,
"Anthropic automatic prompt caching enabled"
);
}
tracing::info!(
tracing::debug!(
provider = %config.provider_id,
model = %config.model,
base_url = if config.base_url.is_empty() { "default" } else { &config.base_url },
@@ -317,7 +317,7 @@ fn create_ollama_from_registry(
let model = client.completion_model(&config.model);
tracing::info!(
tracing::debug!(
provider = %config.provider_id,
model = %config.model,
base_url = %config.base_url,
@@ -385,14 +385,14 @@ pub async fn build_provider_chain(
LlmError,
> {
let llm = create_llm_provider(config, session.clone()).await?;
tracing::info!("LLM provider initialized: {}", llm.model_name());
tracing::debug!("LLM provider initialized: {}", llm.model_name());
// 1. Retry
let retry_config = RetryConfig {
max_retries: config.nearai.max_retries,
};
let llm: Arc<dyn LlmProvider> = if retry_config.max_retries > 0 {
tracing::info!(
tracing::debug!(
max_retries = retry_config.max_retries,
"LLM retry wrapper enabled"
);
@@ -415,7 +415,7 @@ pub async fn build_provider_chain(
} else {
cheap
};
tracing::info!(
tracing::debug!(
primary = %llm.model_name(),
cheap = %cheap.model_name(),
"Smart routing enabled"
@@ -446,7 +446,7 @@ pub async fn build_provider_chain(
session.clone(),
config.request_timeout_secs,
)?;
tracing::info!(
tracing::debug!(
primary = %llm.model_name(),
fallback = %fallback.model_name(),
"LLM failover enabled"
@@ -478,7 +478,7 @@ pub async fn build_provider_chain(
),
..CircuitBreakerConfig::default()
};
tracing::info!(
tracing::debug!(
threshold,
recovery_secs = config.nearai.circuit_breaker_recovery_secs,
"LLM circuit breaker enabled"
@@ -494,7 +494,7 @@ pub async fn build_provider_chain(
ttl: std::time::Duration::from_secs(config.nearai.response_cache_ttl_secs),
max_entries: config.nearai.response_cache_max_entries,
};
tracing::info!(
tracing::debug!(
ttl_secs = config.nearai.response_cache_ttl_secs,
max_entries = config.nearai.response_cache_max_entries,
"LLM response cache enabled"
@@ -515,7 +515,7 @@ pub async fn build_provider_chain(
// Standalone cheap LLM for heartbeat/evaluation (not part of the chain)
let cheap_llm = create_cheap_llm_provider(config, session)?;
if let Some(ref cheap) = cheap_llm {
tracing::info!("Cheap LLM provider initialized: {}", cheap.model_name());
tracing::debug!("Cheap LLM provider initialized: {}", cheap.model_name());
}
Ok((llm, cheap_llm, recording_handle))
+1 -1
View File
@@ -110,7 +110,7 @@ impl NearAiChatProvider {
handle.spawn(async move {
match fetch_pricing(&client, &base_url, api_key.as_ref(), &session).await {
Ok(map) if !map.is_empty() => {
tracing::info!("Loaded NEAR AI pricing for {} model(s)", map.len());
tracing::debug!("Loaded NEAR AI pricing for {} model(s)", map.len());
match pricing.write() {
Ok(mut guard) => *guard = map,
Err(poisoned) => *poisoned.into_inner() = map,