fork: rename IronClaw → OptimClaw

Full rename of all identifiers, filenames, and references:
  ironclaw → optimclaw
  IronClaw → OptimClaw
  IRONCLAW → OPTIMCLAW
  ironclaw_common → optimclaw_common
  ironclaw_safety → optimclaw_safety

Upstream: nearai/ironclaw
This commit is contained in:
OutBack Dingo
2026-03-29 06:27:52 +07:00
parent 8a320ae9db
commit 6d9dbbb3b9
404 changed files with 2382 additions and 2382 deletions
+7 -7
View File
@@ -1,6 +1,6 @@
# IronClaw Network Security Reference
# OptimClaw Network Security Reference
This document catalogs every network-facing surface in IronClaw, its authentication mechanism, bind address, security controls, and known findings. Use this as the authoritative reference during code reviews that touch network-facing code.
This document catalogs every network-facing surface in OptimClaw, its authentication mechanism, bind address, security controls, and known findings. Use this as the authoritative reference during code reviews that touch network-facing code.
**Last updated:** 2026-02-18
@@ -8,7 +8,7 @@ This document catalogs every network-facing surface in IronClaw, its authenticat
## Threat Model
IronClaw operates across four trust boundaries:
OptimClaw operates across four trust boundaries:
| Boundary | Trust Level | Examples |
|----------|------------|---------|
@@ -21,7 +21,7 @@ IronClaw operates across four trust boundaries:
- The local machine is single-user. The web gateway and OAuth listener bind to loopback and do not defend against other local users.
- Docker containers are adversarial. A compromised container should not be able to access other jobs, exfiltrate secrets, or reach the host network beyond the orchestrator API.
- Webhook senders must prove knowledge of the shared secret. The secret is never transmitted in the clear by IronClaw itself.
- Webhook senders must prove knowledge of the shared secret. The secret is never transmitted in the clear by OptimClaw itself.
- MCP server URLs are operator-configured and treated as trusted destinations (see [MCP Client](#mcp-client)).
---
@@ -272,7 +272,7 @@ Both IPv4 and IPv6 loopback addresses are security-equivalent — they are only
### Lifecycle
The listener is **ephemeral** — it is started only when an OAuth flow is initiated (e.g., `ironclaw tool auth <name>`) and shut down after the callback is received or the timeout expires.
The listener is **ephemeral** — it is started only when an OAuth flow is initiated (e.g., `optimclaw tool auth <name>`) and shut down after the callback is received or the timeout expires.
### Timeout
@@ -290,7 +290,7 @@ The listener is **ephemeral** — it is started only when an OAuth flow is initi
### Built-in OAuth Credentials
Google OAuth client ID and secret are compiled into the binary (with compile-time override via `IRONCLAW_GOOGLE_CLIENT_ID` / `IRONCLAW_GOOGLE_CLIENT_SECRET`). As noted in the source, Google Desktop App client secrets are [not actually secret](https://developers.google.com/identity/protocols/oauth2/native-app) per Google's documentation.
Google OAuth client ID and secret are compiled into the binary (with compile-time override via `OPTIMCLAW_GOOGLE_CLIENT_ID` / `OPTIMCLAW_GOOGLE_CLIENT_SECRET`). As noted in the source, Google Desktop App client secrets are [not actually secret](https://developers.google.com/identity/protocols/oauth2/native-app) per Google's documentation.
**Reference:** `src/cli/oauth_defaults.rs``GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` constants
@@ -427,7 +427,7 @@ The `http` tool (`src/tools/builtin/http.rs`) has its own SSRF protections:
MCP servers are external processes accessed via HTTP. The MCP client (`src/tools/mcp/client.rs`) uses `reqwest` with a 30-second timeout but has **no SSRF protections** — it connects to whatever URL is configured for the MCP server.
This is by design: MCP server URLs come from **operator-controlled configuration** (config files, environment variables, or the CLI `tool install` command), not from user input or LLM output. A compromised config file is outside IronClaw's threat model — it would imply the operator's machine is already compromised.
This is by design: MCP server URLs come from **operator-controlled configuration** (config files, environment variables, or the CLI `tool install` command), not from user input or LLM output. A compromised config file is outside OptimClaw's threat model — it would imply the operator's machine is already compromised.
**Reference:** `src/tools/mcp/client.rs``reqwest::Client` builder
+5 -5
View File
@@ -625,11 +625,11 @@ impl Agent {
));
}
// Environment check: restart is only available in Docker containers
let in_docker = std::env::var("IRONCLAW_IN_DOCKER")
let in_docker = std::env::var("OPTIMCLAW_IN_DOCKER")
.map(|v| v.to_lowercase() == "true")
.unwrap_or(false);
tracing::debug!("[commands::restart] IRONCLAW_IN_DOCKER={}", in_docker);
tracing::debug!("[commands::restart] OPTIMCLAW_IN_DOCKER={}", in_docker);
if !in_docker {
tracing::warn!(
@@ -637,7 +637,7 @@ impl Agent {
);
return Ok(SubmissionResult::error(
"Restart is not available in this environment. \
The IRONCLAW_IN_DOCKER environment variable must be set to 'true' for Docker deployments."
The OPTIMCLAW_IN_DOCKER environment variable must be set to 'true' for Docker deployments."
.to_string(),
));
}
@@ -976,7 +976,7 @@ impl Agent {
let model_owned = model.to_string();
let backend = self.deps.llm_backend.clone();
if let Err(e) = tokio::task::spawn_blocking(move || {
// 2a. Update the backend-specific model env var in ~/.ironclaw/.env.
// 2a. Update the backend-specific model env var in ~/.optimclaw/.env.
//
// Env vars have the HIGHEST priority in LlmConfig::resolve_model()
// (env var > TOML > DB > default). If the .env file has e.g.
@@ -988,7 +988,7 @@ impl Agent {
// Only update the .env file if the var is actually set there
// (avoid injecting new vars the user never configured).
let env_path = crate::bootstrap::ironclaw_env_path();
let env_path = crate::bootstrap::optimclaw_env_path();
let env_has_var = std::fs::read_to_string(&env_path)
.ok()
.is_some_and(|content| {
+1 -1
View File
@@ -22,7 +22,7 @@ use uuid::Uuid;
use crate::channels::IncomingMessage;
use crate::context::{ContextManager, JobState};
use ironclaw_common::AppEvent;
use optimclaw_common::AppEvent;
/// Route context for forwarding job monitor events back to the user's channel.
#[derive(Debug, Clone)]
+1 -1
View File
@@ -760,7 +760,7 @@ mod tests {
#[test]
fn test_system_event_trigger_roundtrip() {
let mut filters = std::collections::HashMap::new();
filters.insert("repo".to_string(), "nearai/ironclaw".to_string());
filters.insert("repo".to_string(), "nearai/optimclaw".to_string());
filters.insert("action".to_string(), "opened".to_string());
let trigger = Trigger::SystemEvent {
source: "github".to_string(),
+1 -1
View File
@@ -39,7 +39,7 @@ use crate::tools::{
prepare_tool_params,
};
use crate::workspace::Workspace;
use ironclaw_safety::SafetyLayer;
use optimclaw_safety::SafetyLayer;
enum EventMatcher {
Message { routine: Routine, regex: Regex },
+1 -1
View File
@@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::llm::{ChatMessage, ToolCall, generate_tool_call_id};
use ironclaw_common::truncate_preview;
use optimclaw_common::truncate_preview;
/// A session containing one or more threads.
#[derive(Debug, Clone, Serialize, Deserialize)]
+1 -1
View File
@@ -21,7 +21,7 @@ use crate::context::JobContext;
use crate::error::Error;
use crate::llm::{ChatMessage, ToolCall};
use crate::tools::redact_params;
use ironclaw_common::truncate_preview;
use optimclaw_common::truncate_preview;
const FORGED_THREAD_ID_ERROR: &str = "Invalid or unauthorized thread ID.";
+2 -2
View File
@@ -1,4 +1,4 @@
//! Application builder for initializing core IronClaw components.
//! Application builder for initializing core OptimClaw components.
//!
//! Extracts the mechanical initialization phases from `main.rs` into a
//! reusable builder so that:
@@ -602,7 +602,7 @@ impl AppBuilder {
{
tracing::warn!(
"MCP server '{}' requires authentication. \
Run: ironclaw mcp auth {}",
Run: optimclaw mcp auth {}",
server_name,
server_name
);
+6 -6
View File
@@ -3,7 +3,7 @@
//! Shows a compact ANSI-styled status panel with three tiers:
//! - **Tier 1 (always):** Name + version, model + backend.
//! - **Tier 2 (conditional):** Gateway URL, tunnel URL, non-default channels.
//! - **Tier 3 (removed):** Database, tool count, features → use `ironclaw status`.
//! - **Tier 3 (removed):** Database, tool count, features → use `optimclaw status`.
use crate::cli::fmt;
@@ -42,7 +42,7 @@ const KW: usize = 10;
///
/// **Tier 1 (always):** Name + version, model + backend.
/// **Tier 2 (conditional):** Gateway URL, tunnel URL, non-default channels.
/// **Tier 3 (removed):** Database, tool count, features — use `ironclaw status`.
/// **Tier 3 (removed):** Database, tool count, features — use `optimclaw status`.
pub fn print_boot_screen(info: &BootInfo) {
let border = format!(" {}", fmt::separator(58));
@@ -236,9 +236,9 @@ pub fn print_boot_screen(info: &BootInfo) {
println!(" {}ready in {}{}", fmt::dim(), elapsed_str, fmt::reset());
}
// Hint to run `ironclaw status` for full details
// Hint to run `optimclaw status` for full details
println!(
" {}Run `ironclaw status` for full system details.{}",
" {}Run `optimclaw status` for full system details.{}",
fmt::hint(),
fmt::reset()
);
@@ -255,7 +255,7 @@ mod tests {
fn test_print_boot_screen_full() {
let info = BootInfo {
version: "0.2.0".to_string(),
agent_name: "ironclaw".to_string(),
agent_name: "optimclaw".to_string(),
llm_backend: "nearai".to_string(),
llm_model: "claude-3-5-sonnet-20241022".to_string(),
cheap_model: Some("gpt-4o-mini".to_string()),
@@ -289,7 +289,7 @@ mod tests {
fn test_print_boot_screen_minimal() {
let info = BootInfo {
version: "0.2.0".to_string(),
agent_name: "ironclaw".to_string(),
agent_name: "optimclaw".to_string(),
llm_backend: "nearai".to_string(),
llm_model: "gpt-4o".to_string(),
cheap_model: None,
+122 -122
View File
@@ -1,33 +1,33 @@
//! Bootstrap helpers for IronClaw.
//! Bootstrap helpers for OptimClaw.
//!
//! The only setting that truly needs disk persistence before the database is
//! available is `DATABASE_URL` (chicken-and-egg: can't connect to DB without
//! it). Everything else is auto-detected or read from env vars.
//!
//! File: `~/.ironclaw/.env` (standard dotenvy format)
//! File: `~/.optimclaw/.env` (standard dotenvy format)
use std::path::PathBuf;
use std::sync::LazyLock;
const IRONCLAW_BASE_DIR_ENV: &str = "IRONCLAW_BASE_DIR";
const OPTIMCLAW_BASE_DIR_ENV: &str = "OPTIMCLAW_BASE_DIR";
/// Lazily computed IronClaw base directory, cached for the lifetime of the process.
static IRONCLAW_BASE_DIR: LazyLock<PathBuf> = LazyLock::new(compute_ironclaw_base_dir);
/// Lazily computed OptimClaw base directory, cached for the lifetime of the process.
static OPTIMCLAW_BASE_DIR: LazyLock<PathBuf> = LazyLock::new(compute_optimclaw_base_dir);
/// Compute the IronClaw base directory from environment.
/// Compute the OptimClaw base directory from environment.
///
/// This is the underlying implementation used by both the public
/// `ironclaw_base_dir()` function (which caches the result) and tests
/// `optimclaw_base_dir()` function (which caches the result) and tests
/// (which need to verify different configurations).
pub fn compute_ironclaw_base_dir() -> PathBuf {
std::env::var(IRONCLAW_BASE_DIR_ENV)
pub fn compute_optimclaw_base_dir() -> PathBuf {
std::env::var(OPTIMCLAW_BASE_DIR_ENV)
.map(PathBuf::from)
.map(|path| {
if path.as_os_str().is_empty() {
default_base_dir()
} else if !path.is_absolute() {
eprintln!(
"Warning: IRONCLAW_BASE_DIR is a relative path '{}', resolved against current directory",
"Warning: OPTIMCLAW_BASE_DIR is a relative path '{}', resolved against current directory",
path.display()
);
path
@@ -38,64 +38,64 @@ pub fn compute_ironclaw_base_dir() -> PathBuf {
.unwrap_or_else(|_| default_base_dir())
}
/// Get the default IronClaw base directory (~/.ironclaw).
/// Get the default OptimClaw base directory (~/.optimclaw).
///
/// Logs a warning if the home directory cannot be determined and falls back to
/// the current directory.
fn default_base_dir() -> PathBuf {
if let Some(home) = dirs::home_dir() {
home.join(".ironclaw")
home.join(".optimclaw")
} else {
eprintln!("Warning: Could not determine home directory, using current directory");
std::env::current_dir()
.unwrap_or_else(|_| PathBuf::from("/tmp"))
.join(".ironclaw")
.join(".optimclaw")
}
}
/// Get the IronClaw base directory.
/// Get the OptimClaw base directory.
///
/// Override with `IRONCLAW_BASE_DIR` environment variable.
/// Defaults to `~/.ironclaw` (or `./.ironclaw` if home directory cannot be determined).
/// Override with `OPTIMCLAW_BASE_DIR` environment variable.
/// Defaults to `~/.optimclaw` (or `./.optimclaw` if home directory cannot be determined).
///
/// Thread-safe: the value is computed once and cached in a `LazyLock`.
///
/// # Environment Variable Behavior
/// - If `IRONCLAW_BASE_DIR` is set to a non-empty path, that path is used.
/// - If `IRONCLAW_BASE_DIR` is set to an empty string, it is treated as unset.
/// - If `IRONCLAW_BASE_DIR` contains null bytes, a warning is printed and the default is used.
/// - If `OPTIMCLAW_BASE_DIR` is set to a non-empty path, that path is used.
/// - If `OPTIMCLAW_BASE_DIR` is set to an empty string, it is treated as unset.
/// - If `OPTIMCLAW_BASE_DIR` contains null bytes, a warning is printed and the default is used.
/// - If the home directory cannot be determined, a warning is printed and the current directory is used.
///
/// # Returns
/// A `PathBuf` pointing to the base directory. The path is not validated
/// for existence.
pub fn ironclaw_base_dir() -> PathBuf {
IRONCLAW_BASE_DIR.clone()
pub fn optimclaw_base_dir() -> PathBuf {
OPTIMCLAW_BASE_DIR.clone()
}
/// Path to the IronClaw-specific `.env` file: `~/.ironclaw/.env`.
pub fn ironclaw_env_path() -> PathBuf {
ironclaw_base_dir().join(".env")
/// Path to the OptimClaw-specific `.env` file: `~/.optimclaw/.env`.
pub fn optimclaw_env_path() -> PathBuf {
optimclaw_base_dir().join(".env")
}
/// Load env vars from `~/.ironclaw/.env` (in addition to the standard `.env`).
/// Load env vars from `~/.optimclaw/.env` (in addition to the standard `.env`).
///
/// Call this **after** `dotenvy::dotenv()` so that the standard `./.env`
/// takes priority over `~/.ironclaw/.env`. dotenvy never overwrites
/// takes priority over `~/.optimclaw/.env`. dotenvy never overwrites
/// existing env vars, so the effective priority is:
///
/// explicit env vars > `./.env` > `~/.ironclaw/.env` > auto-detect
/// explicit env vars > `./.env` > `~/.optimclaw/.env` > auto-detect
///
/// If `~/.ironclaw/.env` doesn't exist but the legacy `bootstrap.json` does,
/// If `~/.optimclaw/.env` doesn't exist but the legacy `bootstrap.json` does,
/// extracts `DATABASE_URL` from it and writes the `.env` file (one-time
/// upgrade from the old config format).
///
/// After loading the `.env` file, auto-detects the libsql backend: if
/// `DATABASE_BACKEND` is still unset and `~/.ironclaw/ironclaw.db` exists,
/// `DATABASE_BACKEND` is still unset and `~/.optimclaw/optimclaw.db` exists,
/// defaults to `libsql` so cloud instances work out of the box without any
/// manual configuration.
pub fn load_ironclaw_env() {
let path = ironclaw_env_path();
pub fn load_optimclaw_env() {
let path = optimclaw_env_path();
if !path.exists() {
// One-time upgrade: extract DATABASE_URL from legacy bootstrap.json
@@ -109,18 +109,18 @@ pub fn load_ironclaw_env() {
// Auto-detect libsql: if DATABASE_BACKEND is still unset after loading
// all env files, and the local SQLite DB exists, default to libsql.
// This avoids the chicken-and-egg problem on cloud instances where no
// DATABASE_URL is configured but ironclaw.db is already present.
// DATABASE_URL is configured but optimclaw.db is already present.
if std::env::var("DATABASE_BACKEND").is_err() {
let default_db = dirs::home_dir()
.unwrap_or_default()
.join(".ironclaw")
.join("ironclaw.db");
.join(".optimclaw")
.join("optimclaw.db");
if default_db.exists() {
if tokio::runtime::Handle::try_current().is_ok() {
// Tokio runtime is active (multi-threaded); std::env::set_var is UB here.
// Fall back to the thread-safe runtime overlay so the value is always set.
tracing::warn!(
"load_ironclaw_env called with active Tokio runtime; \
"load_optimclaw_env called with active Tokio runtime; \
using runtime env overlay for DATABASE_BACKEND"
);
crate::config::set_runtime_env("DATABASE_BACKEND", "libsql");
@@ -134,10 +134,10 @@ pub fn load_ironclaw_env() {
/// If `bootstrap.json` exists, pull `database_url` out of it and write `.env`.
fn migrate_bootstrap_json_to_env(env_path: &std::path::Path) {
let ironclaw_dir = env_path
let optimclaw_dir = env_path
.parent()
.unwrap_or_else(|| std::path::Path::new("."));
let bootstrap_path = ironclaw_dir.join("bootstrap.json");
let bootstrap_path = optimclaw_dir.join("bootstrap.json");
if !bootstrap_path.exists() {
return;
@@ -173,7 +173,7 @@ fn migrate_bootstrap_json_to_env(env_path: &std::path::Path) {
}
}
/// Write database bootstrap vars to `~/.ironclaw/.env`.
/// Write database bootstrap vars to `~/.optimclaw/.env`.
///
/// These settings form the chicken-and-egg layer: they must be available
/// from the filesystem (env vars) BEFORE any database connection, because
@@ -184,7 +184,7 @@ fn migrate_bootstrap_json_to_env(env_path: &std::path::Path) {
/// Values are double-quoted so that `#` (common in URL-encoded passwords)
/// and other shell-special characters are preserved by dotenvy.
pub fn save_bootstrap_env(vars: &[(&str, &str)]) -> std::io::Result<()> {
save_bootstrap_env_to(&ironclaw_env_path(), vars)
save_bootstrap_env_to(&optimclaw_env_path(), vars)
}
/// Write bootstrap vars to an arbitrary path (testable variant).
@@ -207,13 +207,13 @@ pub fn save_bootstrap_env_to(path: &std::path::Path, vars: &[(&str, &str)]) -> s
Ok(())
}
/// Update or add multiple variables in `~/.ironclaw/.env`, preserving existing content.
/// Update or add multiple variables in `~/.optimclaw/.env`, preserving existing content.
///
/// Like `upsert_bootstrap_var` but batched — replaces lines for any key in `vars`
/// and preserves all other existing lines. Use this instead of `save_bootstrap_env`
/// when you want to update specific keys without destroying user-added variables.
pub fn upsert_bootstrap_vars(vars: &[(&str, &str)]) -> std::io::Result<()> {
upsert_bootstrap_vars_to(&ironclaw_env_path(), vars)
upsert_bootstrap_vars_to(&optimclaw_env_path(), vars)
}
/// Update or add multiple variables at an arbitrary path (testable variant).
@@ -259,14 +259,14 @@ pub fn upsert_bootstrap_vars_to(
Ok(())
}
/// Update or add a single variable in `~/.ironclaw/.env`, preserving existing content.
/// Update or add a single variable in `~/.optimclaw/.env`, preserving existing content.
///
/// Unlike `save_bootstrap_env` (which overwrites the entire file), this
/// reads the current `.env`, replaces the line for `key` if it exists,
/// or appends it otherwise. Use this when writing a single bootstrap var
/// outside the wizard (which manages the full set via `save_bootstrap_env`).
pub fn upsert_bootstrap_var(key: &str, value: &str) -> std::io::Result<()> {
upsert_bootstrap_var_to(&ironclaw_env_path(), key, value)
upsert_bootstrap_var_to(&optimclaw_env_path(), key, value)
}
/// Update or add a single variable at an arbitrary path (testable variant).
@@ -325,7 +325,7 @@ fn restrict_file_permissions(_path: &std::path::Path) -> std::io::Result<()> {
Ok(())
}
/// Write `DATABASE_URL` to `~/.ironclaw/.env`.
/// Write `DATABASE_URL` to `~/.optimclaw/.env`.
///
/// Convenience wrapper around `save_bootstrap_env` for single-value migration
/// paths. Prefer `save_bootstrap_env` for new code.
@@ -333,7 +333,7 @@ pub fn save_database_url(url: &str) -> std::io::Result<()> {
save_bootstrap_env(&[("DATABASE_URL", url)])
}
/// One-time migration of legacy `~/.ironclaw/settings.json` into the database.
/// One-time migration of legacy `~/.optimclaw/settings.json` into the database.
///
/// Only runs when a `settings.json` exists on disk AND the DB has no settings
/// yet. After the wizard writes directly to the DB, this path is only hit by
@@ -344,8 +344,8 @@ pub async fn migrate_disk_to_db(
store: &dyn crate::db::Database,
user_id: &str,
) -> Result<(), MigrationError> {
let ironclaw_dir = ironclaw_base_dir();
let legacy_settings_path = ironclaw_dir.join("settings.json");
let optimclaw_dir = optimclaw_base_dir();
let legacy_settings_path = optimclaw_dir.join("settings.json");
if !legacy_settings_path.exists() {
tracing::debug!("No legacy settings.json found, skipping disk-to-DB migration");
@@ -378,15 +378,15 @@ pub async fn migrate_disk_to_db(
tracing::info!("Migrated {} settings to database", db_map.len());
}
// 2. Write DATABASE_URL to ~/.ironclaw/.env
// 2. Write DATABASE_URL to ~/.optimclaw/.env
if let Some(ref url) = settings.database_url {
save_database_url(url)
.map_err(|e| MigrationError::Io(format!("Failed to write .env: {}", e)))?;
tracing::info!("Wrote DATABASE_URL to {}", ironclaw_env_path().display());
tracing::info!("Wrote DATABASE_URL to {}", optimclaw_env_path().display());
}
// 3. Migrate mcp-servers.json if it exists
let mcp_path = ironclaw_dir.join("mcp-servers.json");
let mcp_path = optimclaw_dir.join("mcp-servers.json");
if mcp_path.exists() {
match std::fs::read_to_string(&mcp_path) {
Ok(content) => match serde_json::from_str::<serde_json::Value>(&content) {
@@ -415,7 +415,7 @@ pub async fn migrate_disk_to_db(
}
// 4. Migrate session.json if it exists
let session_path = ironclaw_dir.join("session.json");
let session_path = optimclaw_dir.join("session.json");
if session_path.exists() {
match std::fs::read_to_string(&session_path) {
Ok(content) => match serde_json::from_str::<serde_json::Value>(&content) {
@@ -447,7 +447,7 @@ pub async fn migrate_disk_to_db(
rename_to_migrated(&legacy_settings_path);
// 6. Clean up old bootstrap.json if it exists (superseded by .env)
let old_bootstrap = ironclaw_dir.join("bootstrap.json");
let old_bootstrap = optimclaw_dir.join("bootstrap.json");
if old_bootstrap.exists() {
rename_to_migrated(&old_bootstrap);
tracing::info!("Renamed old bootstrap.json to .migrated");
@@ -477,12 +477,12 @@ pub enum MigrationError {
// ── PID Lock ──────────────────────────────────────────────────────────────
/// Path to the PID lock file: `~/.ironclaw/ironclaw.pid`.
/// Path to the PID lock file: `~/.optimclaw/optimclaw.pid`.
pub fn pid_lock_path() -> PathBuf {
ironclaw_base_dir().join("ironclaw.pid")
optimclaw_base_dir().join("optimclaw.pid")
}
/// A PID-based lock that prevents multiple IronClaw instances from running
/// A PID-based lock that prevents multiple OptimClaw instances from running
/// simultaneously.
///
/// Uses `fs4::try_lock_exclusive()` for atomic locking (no TOCTOU race),
@@ -499,7 +499,7 @@ pub struct PidLock {
/// Errors from PID lock acquisition.
#[derive(Debug, thiserror::Error)]
pub enum PidLockError {
#[error("Another IronClaw instance is already running (PID {pid})")]
#[error("Another OptimClaw instance is already running (PID {pid})")]
AlreadyRunning { pid: u32 },
#[error("Failed to acquire PID lock: {0}")]
Io(#[from] std::io::Error),
@@ -580,14 +580,14 @@ mod tests {
let env_path = dir.path().join(".env");
// Write in the quoted format that save_database_url uses
let url = "postgres://localhost:5432/ironclaw_test";
let url = "postgres://localhost:5432/optimclaw_test";
std::fs::write(&env_path, format!("DATABASE_URL=\"{}\"\n", url)).unwrap();
// Verify the content is a valid dotenv line (quoted)
let content = std::fs::read_to_string(&env_path).unwrap();
assert_eq!(
content,
"DATABASE_URL=\"postgres://localhost:5432/ironclaw_test\"\n"
"DATABASE_URL=\"postgres://localhost:5432/optimclaw_test\"\n"
);
// Verify dotenvy can parse it (strips quotes automatically)
@@ -607,7 +607,7 @@ mod tests {
// URLs with # in the password are common (URL-encoded special chars).
// Without quoting, dotenvy treats # as a comment delimiter.
let url = "postgres://user:p%23ss@localhost:5432/ironclaw";
let url = "postgres://user:p%23ss@localhost:5432/optimclaw";
std::fs::write(&env_path, format!("DATABASE_URL=\"{}\"\n", url)).unwrap();
let parsed: Vec<(String, String)> = dotenvy::from_path_iter(&env_path)
@@ -666,23 +666,23 @@ INJECTED="pwned"#;
}
#[test]
fn test_ironclaw_env_path() {
// Use compute_ironclaw_base_dir() directly to avoid LazyLock caching,
fn test_optimclaw_env_path() {
// Use compute_optimclaw_base_dir() directly to avoid LazyLock caching,
// which can be poisoned by whichever test initializes it first.
let _guard = lock_env();
let old_val = std::env::var("IRONCLAW_BASE_DIR").ok();
let old_val = std::env::var("OPTIMCLAW_BASE_DIR").ok();
// SAFETY: Under lock_env(), no concurrent env access.
unsafe { std::env::remove_var("IRONCLAW_BASE_DIR") };
unsafe { std::env::remove_var("OPTIMCLAW_BASE_DIR") };
let path = compute_ironclaw_base_dir().join(".env");
let path = compute_optimclaw_base_dir().join(".env");
assert!(
path.ends_with(".ironclaw/.env"),
"expected path ending with .ironclaw/.env, got: {}",
path.ends_with(".optimclaw/.env"),
"expected path ending with .optimclaw/.env, got: {}",
path.display()
);
if let Some(val) = old_val {
unsafe { std::env::set_var("IRONCLAW_BASE_DIR", val) };
unsafe { std::env::set_var("OPTIMCLAW_BASE_DIR", val) };
}
}
@@ -694,7 +694,7 @@ INJECTED="pwned"#;
// Write a legacy bootstrap.json
let bootstrap_json = serde_json::json!({
"database_url": "postgres://localhost/ironclaw_upgrade",
"database_url": "postgres://localhost/optimclaw_upgrade",
"database_pool_size": 5,
"secrets_master_key_source": "keychain",
"onboard_completed": true
@@ -716,7 +716,7 @@ INJECTED="pwned"#;
let content = std::fs::read_to_string(&env_path).unwrap();
assert_eq!(
content,
"DATABASE_URL=\"postgres://localhost/ironclaw_upgrade\"\n"
"DATABASE_URL=\"postgres://localhost/optimclaw_upgrade\"\n"
);
// bootstrap.json should be renamed to .migrated
@@ -769,7 +769,7 @@ INJECTED="pwned"#;
let vars = [
("DATABASE_BACKEND", "libsql"),
("LIBSQL_PATH", "/home/user/.ironclaw/ironclaw.db"),
("LIBSQL_PATH", "/home/user/.optimclaw/optimclaw.db"),
];
// Write manually to the temp path (save_bootstrap_env uses the global path)
@@ -793,7 +793,7 @@ INJECTED="pwned"#;
parsed[1],
(
"LIBSQL_PATH".to_string(),
"/home/user/.ironclaw/ironclaw.db".to_string()
"/home/user/.optimclaw/optimclaw.db".to_string()
)
);
}
@@ -855,7 +855,7 @@ INJECTED="pwned"#;
unsafe { std::env::remove_var("DATABASE_BACKEND") };
let dir = tempdir().unwrap();
let db_path = dir.path().join("ironclaw.db");
let db_path = dir.path().join("optimclaw.db");
// No DB file — auto-detect guard should not trigger.
assert!(!db_path.exists());
@@ -926,7 +926,7 @@ INJECTED="pwned"#;
unsafe { std::env::set_var("DATABASE_BACKEND", "postgres") };
let dir = tempdir().unwrap();
let db_path = dir.path().join("ironclaw.db");
let db_path = dir.path().join("optimclaw.db");
std::fs::write(&db_path, "").unwrap();
// The guard: only sets libsql if DATABASE_BACKEND is NOT already set.
@@ -1044,102 +1044,102 @@ INJECTED="pwned"#;
}
#[test]
fn test_ironclaw_base_dir_default() {
fn test_optimclaw_base_dir_default() {
// This test must run first (or in isolation) before the LazyLock is initialized.
// It verifies that when IRONCLAW_BASE_DIR is not set, the default path is used.
// It verifies that when OPTIMCLAW_BASE_DIR is not set, the default path is used.
let _guard = lock_env();
let old_val = std::env::var("IRONCLAW_BASE_DIR").ok();
let old_val = std::env::var("OPTIMCLAW_BASE_DIR").ok();
// SAFETY: ENV_MUTEX ensures single-threaded access to env vars in tests
unsafe { std::env::remove_var("IRONCLAW_BASE_DIR") };
unsafe { std::env::remove_var("OPTIMCLAW_BASE_DIR") };
// Force re-evaluation by calling the computation function directly
let path = compute_ironclaw_base_dir();
let path = compute_optimclaw_base_dir();
let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("."));
assert_eq!(path, home.join(".ironclaw"));
assert_eq!(path, home.join(".optimclaw"));
if let Some(val) = old_val {
// SAFETY: ENV_MUTEX ensures single-threaded access to env vars in tests
unsafe { std::env::set_var("IRONCLAW_BASE_DIR", val) };
unsafe { std::env::set_var("OPTIMCLAW_BASE_DIR", val) };
}
}
#[test]
fn test_ironclaw_base_dir_env_override() {
// This test verifies that when IRONCLAW_BASE_DIR is set,
fn test_optimclaw_base_dir_env_override() {
// This test verifies that when OPTIMCLAW_BASE_DIR is set,
// the custom path is used. Must run before LazyLock is initialized.
let _guard = lock_env();
let old_val = std::env::var("IRONCLAW_BASE_DIR").ok();
let old_val = std::env::var("OPTIMCLAW_BASE_DIR").ok();
// SAFETY: ENV_MUTEX ensures single-threaded access to env vars in tests
unsafe { std::env::set_var("IRONCLAW_BASE_DIR", "/custom/ironclaw/path") };
unsafe { std::env::set_var("OPTIMCLAW_BASE_DIR", "/custom/optimclaw/path") };
// Force re-evaluation by calling the computation function directly
let path = compute_ironclaw_base_dir();
assert_eq!(path, std::path::PathBuf::from("/custom/ironclaw/path"));
let path = compute_optimclaw_base_dir();
assert_eq!(path, std::path::PathBuf::from("/custom/optimclaw/path"));
if let Some(val) = old_val {
// SAFETY: ENV_MUTEX ensures single-threaded access to env vars in tests
unsafe { std::env::set_var("IRONCLAW_BASE_DIR", val) };
unsafe { std::env::set_var("OPTIMCLAW_BASE_DIR", val) };
} else {
// SAFETY: ENV_MUTEX ensures single-threaded access to env vars in tests
unsafe { std::env::remove_var("IRONCLAW_BASE_DIR") };
unsafe { std::env::remove_var("OPTIMCLAW_BASE_DIR") };
}
}
#[test]
fn test_compute_base_dir_env_path_join() {
// Verifies that ironclaw_env_path correctly joins .env to the base dir.
// Uses compute_ironclaw_base_dir directly to avoid LazyLock caching.
// Verifies that optimclaw_env_path correctly joins .env to the base dir.
// Uses compute_optimclaw_base_dir directly to avoid LazyLock caching.
let _guard = lock_env();
let old_val = std::env::var("IRONCLAW_BASE_DIR").ok();
let old_val = std::env::var("OPTIMCLAW_BASE_DIR").ok();
// SAFETY: ENV_MUTEX ensures single-threaded access to env vars in tests
unsafe { std::env::set_var("IRONCLAW_BASE_DIR", "/my/custom/dir") };
unsafe { std::env::set_var("OPTIMCLAW_BASE_DIR", "/my/custom/dir") };
// Test the path construction logic directly
let base_path = compute_ironclaw_base_dir();
let base_path = compute_optimclaw_base_dir();
let env_path = base_path.join(".env");
assert_eq!(env_path, std::path::PathBuf::from("/my/custom/dir/.env"));
if let Some(val) = old_val {
// SAFETY: ENV_MUTEX ensures single-threaded access to env vars in tests
unsafe { std::env::set_var("IRONCLAW_BASE_DIR", val) };
unsafe { std::env::set_var("OPTIMCLAW_BASE_DIR", val) };
} else {
// SAFETY: ENV_MUTEX ensures single-threaded access to env vars in tests
unsafe { std::env::remove_var("IRONCLAW_BASE_DIR") };
unsafe { std::env::remove_var("OPTIMCLAW_BASE_DIR") };
}
}
#[test]
fn test_ironclaw_base_dir_empty_env() {
// Verifies that empty IRONCLAW_BASE_DIR falls back to default.
fn test_optimclaw_base_dir_empty_env() {
// Verifies that empty OPTIMCLAW_BASE_DIR falls back to default.
let _guard = lock_env();
let old_val = std::env::var("IRONCLAW_BASE_DIR").ok();
let old_val = std::env::var("OPTIMCLAW_BASE_DIR").ok();
// SAFETY: ENV_MUTEX ensures single-threaded access to env vars in tests
unsafe { std::env::set_var("IRONCLAW_BASE_DIR", "") };
unsafe { std::env::set_var("OPTIMCLAW_BASE_DIR", "") };
// Force re-evaluation by calling the computation function directly
let path = compute_ironclaw_base_dir();
let path = compute_optimclaw_base_dir();
let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("."));
assert_eq!(path, home.join(".ironclaw"));
assert_eq!(path, home.join(".optimclaw"));
if let Some(val) = old_val {
// SAFETY: ENV_MUTEX ensures single-threaded access to env vars in tests
unsafe { std::env::set_var("IRONCLAW_BASE_DIR", val) };
unsafe { std::env::set_var("OPTIMCLAW_BASE_DIR", val) };
} else {
// SAFETY: ENV_MUTEX ensures single-threaded access to env vars in tests
unsafe { std::env::remove_var("IRONCLAW_BASE_DIR") };
unsafe { std::env::remove_var("OPTIMCLAW_BASE_DIR") };
}
}
#[test]
fn test_ironclaw_base_dir_special_chars() {
fn test_optimclaw_base_dir_special_chars() {
// Verifies that paths with special characters are handled correctly.
let _guard = lock_env();
let old_val = std::env::var("IRONCLAW_BASE_DIR").ok();
let old_val = std::env::var("OPTIMCLAW_BASE_DIR").ok();
// SAFETY: ENV_MUTEX ensures single-threaded access to env vars in tests
unsafe { std::env::set_var("IRONCLAW_BASE_DIR", "/tmp/test_with-special.chars") };
unsafe { std::env::set_var("OPTIMCLAW_BASE_DIR", "/tmp/test_with-special.chars") };
// Force re-evaluation by calling the computation function directly
let path = compute_ironclaw_base_dir();
let path = compute_optimclaw_base_dir();
assert_eq!(
path,
std::path::PathBuf::from("/tmp/test_with-special.chars")
@@ -1147,10 +1147,10 @@ INJECTED="pwned"#;
if let Some(val) = old_val {
// SAFETY: ENV_MUTEX ensures single-threaded access to env vars in tests
unsafe { std::env::set_var("IRONCLAW_BASE_DIR", val) };
unsafe { std::env::set_var("OPTIMCLAW_BASE_DIR", val) };
} else {
// SAFETY: ENV_MUTEX ensures single-threaded access to env vars in tests
unsafe { std::env::remove_var("IRONCLAW_BASE_DIR") };
unsafe { std::env::remove_var("OPTIMCLAW_BASE_DIR") };
}
}
@@ -1159,7 +1159,7 @@ INJECTED="pwned"#;
#[test]
fn test_pid_lock_acquire_and_drop() {
let dir = tempdir().unwrap();
let pid_path = dir.path().join("ironclaw.pid");
let pid_path = dir.path().join("optimclaw.pid");
// Acquire lock
let lock = PidLock::acquire_at(pid_path.clone()).unwrap();
@@ -1177,7 +1177,7 @@ INJECTED="pwned"#;
#[test]
fn test_pid_lock_rejects_second_acquire() {
let dir = tempdir().unwrap();
let pid_path = dir.path().join("ironclaw.pid");
let pid_path = dir.path().join("optimclaw.pid");
// First lock succeeds
let _lock1 = PidLock::acquire_at(pid_path.clone()).unwrap();
@@ -1196,7 +1196,7 @@ INJECTED="pwned"#;
#[test]
fn test_pid_lock_reclaims_after_drop() {
let dir = tempdir().unwrap();
let pid_path = dir.path().join("ironclaw.pid");
let pid_path = dir.path().join("optimclaw.pid");
// Acquire and release
let lock = PidLock::acquire_at(pid_path.clone()).unwrap();
@@ -1210,7 +1210,7 @@ INJECTED="pwned"#;
#[test]
fn test_pid_lock_reclaims_stale_file_without_flock() {
let dir = tempdir().unwrap();
let pid_path = dir.path().join("ironclaw.pid");
let pid_path = dir.path().join("optimclaw.pid");
// Write a stale PID file manually (no flock held)
std::fs::write(&pid_path, "4294967294").unwrap();
@@ -1225,7 +1225,7 @@ INJECTED="pwned"#;
#[test]
fn test_pid_lock_handles_corrupt_pid_file() {
let dir = tempdir().unwrap();
let pid_path = dir.path().join("ironclaw.pid");
let pid_path = dir.path().join("optimclaw.pid");
// Write garbage (no flock held)
std::fs::write(&pid_path, "not-a-number").unwrap();
@@ -1238,7 +1238,7 @@ INJECTED="pwned"#;
#[test]
fn test_pid_lock_creates_parent_dirs() {
let dir = tempdir().unwrap();
let pid_path = dir.path().join("nested").join("deep").join("ironclaw.pid");
let pid_path = dir.path().join("nested").join("deep").join("optimclaw.pid");
let lock = PidLock::acquire_at(pid_path.clone()).unwrap();
assert!(pid_path.exists());
@@ -1247,14 +1247,14 @@ INJECTED="pwned"#;
#[test]
fn test_pid_lock_child_helper_holds_lock() {
if std::env::var("IRONCLAW_PID_LOCK_CHILD").ok().as_deref() != Some("1") {
if std::env::var("OPTIMCLAW_PID_LOCK_CHILD").ok().as_deref() != Some("1") {
return;
}
let pid_path = PathBuf::from(
std::env::var("IRONCLAW_PID_LOCK_PATH").expect("IRONCLAW_PID_LOCK_PATH missing"),
std::env::var("OPTIMCLAW_PID_LOCK_PATH").expect("OPTIMCLAW_PID_LOCK_PATH missing"),
);
let hold_ms = std::env::var("IRONCLAW_PID_LOCK_HOLD_MS")
let hold_ms = std::env::var("OPTIMCLAW_PID_LOCK_HOLD_MS")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(3000);
@@ -1266,7 +1266,7 @@ INJECTED="pwned"#;
#[test]
fn test_pid_lock_rejects_lock_held_by_other_process() {
let dir = tempdir().unwrap();
let pid_path = dir.path().join("ironclaw.pid");
let pid_path = dir.path().join("optimclaw.pid");
let current_exe = std::env::current_exe().unwrap();
let mut child = Command::new(current_exe)
@@ -1276,9 +1276,9 @@ INJECTED="pwned"#;
"--nocapture",
"--test-threads=1",
])
.env("IRONCLAW_PID_LOCK_CHILD", "1")
.env("IRONCLAW_PID_LOCK_PATH", pid_path.display().to_string())
.env("IRONCLAW_PID_LOCK_HOLD_MS", "3000")
.env("OPTIMCLAW_PID_LOCK_CHILD", "1")
.env("OPTIMCLAW_PID_LOCK_PATH", pid_path.display().to_string())
.env("OPTIMCLAW_PID_LOCK_HOLD_MS", "3000")
.spawn()
.unwrap();
+1 -1
View File
@@ -73,7 +73,7 @@ pub struct IncomingMessage {
/// configured owner is speaking; otherwise it can be a guest/sender-scoped
/// identifier to preserve isolation.
pub user_id: String,
/// Stable instance owner scope for this IronClaw deployment.
/// Stable instance owner scope for this OptimClaw deployment.
pub owner_id: String,
/// Channel-specific sender/actor identifier.
pub sender_id: String,
+1 -1
View File
@@ -119,7 +119,7 @@ impl RelayClient {
/// Calls `GET /oauth/slack/auth` with `redirect(Policy::none())` and
/// returns the `Location` header (Slack OAuth URL) without following it.
/// Initiate Slack OAuth. Channel-relay derives all URLs from the trusted
/// instance_url in chat-api. IronClaw only passes an optional CSRF nonce
/// instance_url in chat-api. OptimClaw only passes an optional CSRF nonce
/// for validating the callback — no URLs.
pub async fn initiate_oauth(&self, state_nonce: Option<&str>) -> Result<String, RelayError> {
let url = format!("{}/oauth/slack/auth", self.base_url);
+1 -1
View File
@@ -2,7 +2,7 @@
//! (Slack) via the channel-relay service.
//!
//! The relay service handles OAuth, credential storage, and webhook ingestion.
//! IronClaw receives events via webhook callbacks and sends messages via the
//! OptimClaw receives events via webhook callbacks and sends messages via the
//! relay's proxy API.
pub mod channel;
+5 -5
View File
@@ -39,7 +39,7 @@ use tokio::sync::mpsc;
use tokio_stream::wrappers::ReceiverStream;
use crate::agent::truncate_for_preview;
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::channels::{Channel, IncomingMessage, MessageStream, OutgoingResponse, StatusUpdate};
use crate::cli::fmt;
use crate::error::ChannelError;
@@ -459,7 +459,7 @@ fn print_help() {
let hi = fmt::hint();
println!();
println!(" {h}IronClaw REPL{r}");
println!(" {h}OptimClaw REPL{r}");
println!();
println!(" {h}Quick start{r}");
println!(" {c}/new{r} {hi}Start a new thread{r}");
@@ -479,9 +479,9 @@ fn print_help() {
println!();
}
/// Get the history file path (~/.ironclaw/history).
/// Get the history file path (~/.optimclaw/history).
fn history_path() -> std::path::PathBuf {
ironclaw_base_dir().join("history")
optimclaw_base_dir().join("history")
}
#[async_trait]
@@ -553,7 +553,7 @@ impl Channel for ReplChannel {
if !suppress_banner.load(Ordering::Relaxed) {
println!(
"{}IronClaw{} /help for commands, /quit to exit",
"{}OptimClaw{} /help for commands, /quit to exit",
fmt::bold(),
fmt::reset()
);
+5 -5
View File
@@ -17,7 +17,7 @@ use serde::Deserialize;
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::channels::{Channel, IncomingMessage, MessageStream, OutgoingResponse, StatusUpdate};
use crate::config::SignalConfig;
use crate::error::ChannelError;
@@ -203,7 +203,7 @@ impl SignalChannel {
);
if result.created {
let message = format!(
"To pair with this bot, run: `ironclaw pairing approve signal {}`",
"To pair with this bot, run: `optimclaw pairing approve signal {}`",
result.code
);
let http_url = self.config.http_url.clone();
@@ -555,10 +555,10 @@ impl SignalChannel {
/// Uses the shared path validation logic from path_utils to ensure:
/// - No path traversal attacks (../, URL-encoded, null bytes)
/// - Paths are canonicalized and symlinks resolved
/// - All paths are within ~/.ironclaw/ sandbox
/// - All paths are within ~/.optimclaw/ sandbox
fn validate_attachment_paths(paths: &[String]) -> Result<(), ChannelError> {
// Get the sandbox base directory (same as MessageTool uses)
let base_dir = ironclaw_base_dir();
let base_dir = optimclaw_base_dir();
for path in paths {
crate::tools::builtin::path_utils::validate_path(path, Some(&base_dir)).map_err(
@@ -2678,7 +2678,7 @@ mod tests {
use std::fs;
// Create test files in sandbox
let base_dir = crate::bootstrap::ironclaw_base_dir();
let base_dir = crate::bootstrap::optimclaw_base_dir();
// Create sandbox directory if it doesn't exist (needed for CI)
let _ = fs::create_dir_all(&base_dir);
+2 -2
View File
@@ -33,10 +33,10 @@ pub fn bundled_channel_names() -> Vec<&'static str> {
/// Resolve the channels source directory.
///
/// Checks (in order):
/// 1. `IRONCLAW_CHANNELS_SRC` env var
/// 1. `OPTIMCLAW_CHANNELS_SRC` env var
/// 2. `<CARGO_MANIFEST_DIR>/channels-src/` (dev builds)
fn channels_src_dir() -> PathBuf {
if let Ok(dir) = std::env::var("IRONCLAW_CHANNELS_SRC") {
if let Ok(dir) = std::env::var("OPTIMCLAW_CHANNELS_SRC") {
return PathBuf::from(dir);
}
PathBuf::from(CARGO_MANIFEST_DIR).join("channels-src")
+4 -4
View File
@@ -1,6 +1,6 @@
//! WASM channel loader for loading channels from files or directories.
//!
//! Loads WASM channel modules from the filesystem (default: ~/.ironclaw/channels/).
//! Loads WASM channel modules from the filesystem (default: ~/.optimclaw/channels/).
//! Each channel consists of:
//! - `<name>.wasm` - The compiled WASM component
//! - `<name>.capabilities.json` - Channel capabilities and configuration
@@ -11,7 +11,7 @@ use std::sync::Arc;
use tokio::fs;
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::channels::wasm::capabilities::ChannelCapabilities;
use crate::channels::wasm::error::WasmChannelError;
use crate::channels::wasm::runtime::WasmChannelRuntime;
@@ -416,10 +416,10 @@ pub struct DiscoveredChannel {
/// Get the default channels directory path.
///
/// Returns ~/.ironclaw/channels/
/// Returns ~/.optimclaw/channels/
#[allow(dead_code)]
pub fn default_channels_dir() -> PathBuf {
ironclaw_base_dir().join("channels")
optimclaw_base_dir().join("channels")
}
#[cfg(test)]
+2 -2
View File
@@ -63,14 +63,14 @@
//! # Example Usage
//!
//! ```ignore
//! use ironclaw::channels::wasm::{WasmChannelLoader, WasmChannelRuntime};
//! use optimclaw::channels::wasm::{WasmChannelLoader, WasmChannelRuntime};
//!
//! // Create runtime (can share engine with tool runtime)
//! let runtime = WasmChannelRuntime::new(config)?;
//!
//! // Load channels from directory
//! let loader = WasmChannelLoader::new(runtime, pairing_store, settings_store, owner_scope_id);
//! let channels = loader.load_from_dir(Path::new("~/.ironclaw/channels/")).await?;
//! let channels = loader.load_from_dir(Path::new("~/.optimclaw/channels/")).await?;
//!
//! // Add to channel manager
//! for channel in channels {
+1 -1
View File
@@ -620,7 +620,7 @@ async fn oauth_callback_handler(
height: 100vh; margin: 0; background: #191919; color: white;\">\
<div style=\"text-align: center;\">\
<h1>Connected!</h1>\
<p>You can close this window and return to IronClaw.</p>\
<p>You can close this window and return to OptimClaw.</p>\
</div></body></html>"
.to_string(),
),
+4 -4
View File
@@ -3276,16 +3276,16 @@ fn read_attachments(paths: &[String]) -> Result<Vec<wit_channel::Attachment>, St
let mut total_bytes: u64 = 0;
let tmp_base = std::path::Path::new("/tmp");
let home_base = dirs::home_dir()
.map(|h| h.join(".ironclaw"))
.map(|h| h.join(".optimclaw"))
.unwrap_or_default();
for path in paths {
// Validate paths are under /tmp/ or ~/.ironclaw/ to prevent arbitrary file reads
// Validate paths are under /tmp/ or ~/.optimclaw/ to prevent arbitrary file reads
let validated = crate::tools::builtin::path_utils::validate_path(path, Some(tmp_base))
.or_else(|_| crate::tools::builtin::path_utils::validate_path(path, Some(&home_base)));
let validated = validated.map_err(|e| {
format!(
"Invalid attachment path '{}': must be under /tmp/ or ~/.ironclaw/: {}",
"Invalid attachment path '{}': must be under /tmp/ or ~/.optimclaw/: {}",
path, e
)
})?;
@@ -4778,7 +4778,7 @@ mod tests {
);
assert_eq!(mime_from_extension("noext"), "application/octet-stream");
assert_eq!(
mime_from_extension("/home/user/.ironclaw/screenshot.png"),
mime_from_extension("/home/user/.optimclaw/screenshot.png"),
"image/png"
);
}
+1 -1
View File
@@ -1,7 +1,7 @@
//! Admin secrets provisioning handlers.
//!
//! Allows an admin (typically an application backend) to create, list, and
//! delete secrets on behalf of individual users so their IronClaw agent can
//! delete secrets on behalf of individual users so their OptimClaw agent can
//! call back to external services with per-user credentials.
use std::sync::Arc;
+3 -3
View File
@@ -6,7 +6,7 @@ use axum::{
response::{Html, IntoResponse},
};
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::channels::web::auth::AuthenticatedUser;
use crate::channels::web::types::*;
@@ -61,7 +61,7 @@ pub async fn project_file_handler(
serve_project_file(&project_id, &path).await
}
/// Shared logic: resolve the file inside `~/.ironclaw/projects/{project_id}/`,
/// Shared logic: resolve the file inside `~/.optimclaw/projects/{project_id}/`,
/// guard against path traversal, and stream the content with the right MIME type.
async fn serve_project_file(project_id: &str, path: &str) -> axum::response::Response {
// Reject project_id values that could escape the projects directory.
@@ -73,7 +73,7 @@ async fn serve_project_file(project_id: &str, path: &str) -> axum::response::Res
return (StatusCode::BAD_REQUEST, "Invalid project ID").into_response();
}
let base = ironclaw_base_dir().join("projects").join(project_id);
let base = optimclaw_base_dir().join("projects").join(project_id);
let file_path = base.join(path);
+1 -1
View File
@@ -34,7 +34,7 @@ fn validate_webhook_secret(
return Err((
StatusCode::FORBIDDEN,
"Webhook secret not configured for this routine. \
Set a secret with: ironclaw routine update <id> --webhook-secret <secret>"
Set a secret with: optimclaw routine update <id> --webhook-secret <secret>"
.to_string(),
));
}
+15 -15
View File
@@ -110,7 +110,7 @@ impl Default for LogBroadcaster {
/// Handle for changing the tracing `EnvFilter` at runtime.
///
/// Wraps a `reload::Handle` so the gateway can switch between log levels
/// (e.g. `ironclaw=debug`) without restarting the process.
/// (e.g. `optimclaw=debug`) without restarting the process.
pub struct LogLevelHandle {
handle: reload::Handle<EnvFilter, tracing_subscriber::Registry>,
current_level: Mutex<String>,
@@ -130,7 +130,7 @@ impl LogLevelHandle {
}
}
/// Change the `ironclaw=<level>` directive at runtime.
/// Change the `optimclaw=<level>` directive at runtime.
///
/// `level` must be one of: trace, debug, info, warn, error.
pub fn set_level(&self, level: &str) -> Result<(), String> {
@@ -145,9 +145,9 @@ impl LogLevelHandle {
}
let filter_str = if self.base_filter.is_empty() {
format!("ironclaw={}", level)
format!("optimclaw={}", level)
} else {
format!("ironclaw={},{}", level, self.base_filter)
format!("optimclaw={},{}", level, self.base_filter)
};
let new_filter = EnvFilter::new(&filter_str);
@@ -161,7 +161,7 @@ impl LogLevelHandle {
Ok(())
}
/// Returns the current ironclaw log level (e.g. "info", "debug").
/// Returns the current optimclaw log level (e.g. "info", "debug").
pub fn current_level(&self) -> String {
self.current_level
.lock()
@@ -176,17 +176,17 @@ impl LogLevelHandle {
/// The fmt layer and `WebLogLayer` are attached alongside the reloadable filter.
pub fn init_tracing(log_broadcaster: Arc<LogBroadcaster>) -> Arc<LogLevelHandle> {
let raw_filter =
std::env::var("RUST_LOG").unwrap_or_else(|_| "ironclaw=info,tower_http=warn".to_string());
std::env::var("RUST_LOG").unwrap_or_else(|_| "optimclaw=info,tower_http=warn".to_string());
// Split into the ironclaw directive and "everything else" (base_filter).
let mut ironclaw_level = String::from("info");
// Split into the optimclaw directive and "everything else" (base_filter).
let mut optimclaw_level = String::from("info");
let mut base_parts: Vec<&str> = Vec::new();
for part in raw_filter.split(',') {
let trimmed = part.trim();
if trimmed.starts_with("ironclaw=") {
if let Some(lvl) = trimmed.strip_prefix("ironclaw=") {
ironclaw_level = lvl.to_string();
if trimmed.starts_with("optimclaw=") {
if let Some(lvl) = trimmed.strip_prefix("optimclaw=") {
optimclaw_level = lvl.to_string();
}
} else if !trimmed.is_empty() {
base_parts.push(trimmed);
@@ -199,7 +199,7 @@ pub fn init_tracing(log_broadcaster: Arc<LogBroadcaster>) -> Arc<LogLevelHandle>
let handle = Arc::new(LogLevelHandle::new(
reload_handle,
ironclaw_level,
optimclaw_level,
base_filter,
));
@@ -220,7 +220,7 @@ pub fn init_tracing(log_broadcaster: Arc<LogBroadcaster>) -> Arc<LogLevelHandle>
/// fields from a tracing event.
///
/// The terminal formatter shows something like:
/// INFO ironclaw::agent: Request completed url="http://..." status=200
/// INFO optimclaw::agent: Request completed url="http://..." status=200
///
/// We replicate that by capturing both the message and the extra fields.
struct MessageVisitor {
@@ -336,7 +336,7 @@ mod tests {
broadcaster.send(LogEntry {
level: "WARN".to_string(),
target: "ironclaw::test".to_string(),
target: "optimclaw::test".to_string(),
message: "test warning".to_string(),
timestamp: "2024-01-01T00:00:00.000Z".to_string(),
});
@@ -350,7 +350,7 @@ mod tests {
fn test_log_entry_serialization() {
let entry = LogEntry {
level: "ERROR".to_string(),
target: "ironclaw::agent".to_string(),
target: "optimclaw::agent".to_string(),
message: "something broke".to_string(),
timestamp: "2024-01-01T00:00:00.000Z".to_string(),
};
+1 -1
View File
@@ -1,4 +1,4 @@
//! Web gateway channel for browser-based access to IronClaw.
//! Web gateway channel for browser-based access to OptimClaw.
//!
//! Provides a single-page web UI with:
//! - Chat with the agent (via REST + SSE)
+4 -4
View File
@@ -1,7 +1,7 @@
//! OpenAI-compatible HTTP API (`/v1/chat/completions`, `/v1/models`).
//!
//! This module provides a direct LLM proxy through the web gateway so any
//! standard OpenAI client library can use IronClaw as a backend by simply
//! standard OpenAI client library can use OptimClaw as a backend by simply
//! changing the `base_url`.
use std::sync::Arc;
@@ -712,7 +712,7 @@ async fn handle_streaming(
let sse = Sse::new(stream).keep_alive(KeepAlive::new().text(""));
let mut response = sse.into_response();
response.headers_mut().insert(
"x-ironclaw-streaming",
"x-optimclaw-streaming",
HeaderValue::from_static("simulated"),
);
Ok(response)
@@ -824,7 +824,7 @@ pub async fn models_handler(
"id": name,
"object": "model",
"created": created,
"owned_by": "ironclaw"
"owned_by": "optimclaw"
})
})
.collect(),
@@ -834,7 +834,7 @@ pub async fn models_handler(
"id": model_name,
"object": "model",
"created": created,
"owned_by": "ironclaw"
"owned_by": "optimclaw"
})]
}
Err(e) => return Err(map_llm_error(e)),
+16 -16
View File
@@ -27,7 +27,7 @@ use tower_http::set_header::SetResponseHeaderLayer;
use uuid::Uuid;
use crate::agent::SessionManager;
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::channels::IncomingMessage;
use crate::channels::relay::DEFAULT_RELAY_NAME;
use crate::channels::web::auth::{
@@ -838,7 +838,7 @@ fn oauth_error_page(label: &str) -> axum::response::Response {
/// redirect the user's browser here. The `state` query parameter correlates
/// the callback with a pending OAuth flow registered by `start_wasm_oauth()`.
///
/// Used on hosted instances where `IRONCLAW_OAUTH_CALLBACK_URL` points to
/// Used on hosted instances where `OPTIMCLAW_OAUTH_CALLBACK_URL` points to
/// the gateway (e.g., `https://kind-deer.agent1.near.ai/oauth/callback`).
/// Local/desktop mode continues to use the TCP listener on port 9876.
async fn oauth_callback_handler(
@@ -859,14 +859,14 @@ async fn oauth_callback_handler(
let state_param = match params.get("state") {
Some(s) if !s.is_empty() => s.clone(),
_ => {
return oauth_error_page("IronClaw");
return oauth_error_page("OptimClaw");
}
};
let code = match params.get("code") {
Some(c) if !c.is_empty() => c.clone(),
_ => {
return oauth_error_page("IronClaw");
return oauth_error_page("OptimClaw");
}
};
@@ -874,7 +874,7 @@ async fn oauth_callback_handler(
let ext_mgr = match state.extension_manager.as_ref() {
Some(mgr) => mgr,
None => {
return oauth_error_page("IronClaw");
return oauth_error_page("OptimClaw");
}
};
@@ -888,7 +888,7 @@ async fn oauth_callback_handler(
"OAuth callback received with malformed state"
);
clear_auth_mode(&state, &state.owner_id).await;
return oauth_error_page("IronClaw");
return oauth_error_page("OptimClaw");
}
};
let lookup_key = decoded_state.flow_id.clone();
@@ -909,7 +909,7 @@ async fn oauth_callback_handler(
lookup_key = %redacted_lookup_key,
"OAuth callback received with unknown or expired state"
);
return oauth_error_page("IronClaw");
return oauth_error_page("OptimClaw");
}
};
@@ -1338,7 +1338,7 @@ async fn slack_relay_oauth_callback_handler(
axum::response::Html(
"<html><body style='font-family: system-ui; text-align: center; padding: 60px;'>\
<h2>Slack Connected!</h2>\
<p>You can close this tab and return to IronClaw.</p>\
<p>You can close this tab and return to OptimClaw.</p>\
<script>window.close()</script>\
</body></html>"
.to_string(),
@@ -2235,7 +2235,7 @@ async fn extensions_install_handler(
crate::extensions::ExtensionSource::WasmBuildable { .. } => {
format!(
"'{}' requires building from source. \
Run `ironclaw registry install {}` from the CLI.",
Run `optimclaw registry install {}` from the CLI.",
req.name, req.name
)
}
@@ -2442,7 +2442,7 @@ async fn verify_project_ownership(state: &GatewayState, project_id: &str, user_i
}
}
/// Shared logic: resolve the file inside `~/.ironclaw/projects/{project_id}/`,
/// Shared logic: resolve the file inside `~/.optimclaw/projects/{project_id}/`,
/// guard against path traversal, and stream the content with the right MIME type.
async fn serve_project_file(project_id: &str, path: &str) -> axum::response::Response {
// Reject project_id values that could escape the projects directory.
@@ -2454,7 +2454,7 @@ async fn serve_project_file(project_id: &str, path: &str) -> axum::response::Res
return (StatusCode::BAD_REQUEST, "Invalid project ID").into_response();
}
let base = ironclaw_base_dir().join("projects").join(project_id);
let base = optimclaw_base_dir().join("projects").join(project_id);
let file_path = base.join(path);
@@ -2901,7 +2901,7 @@ async fn gateway_status_handler(
(None, None, None)
};
let restart_enabled = std::env::var("IRONCLAW_IN_DOCKER")
let restart_enabled = std::env::var("OPTIMCLAW_IN_DOCKER")
.map(|v| v.to_lowercase() == "true")
.unwrap_or(false);
@@ -4065,8 +4065,8 @@ mod tests {
// sees a stable proxy URL/token configuration throughout the test.
let _env_guard = crate::config::helpers::lock_env();
let _exchange_url_guard =
set_env_var("IRONCLAW_OAUTH_EXCHANGE_URL", Some(&proxy.base_url()));
let _proxy_auth_guard = set_env_var("IRONCLAW_OAUTH_PROXY_AUTH_TOKEN", None);
set_env_var("OPTIMCLAW_OAUTH_EXCHANGE_URL", Some(&proxy.base_url()));
let _proxy_auth_guard = set_env_var("OPTIMCLAW_OAUTH_PROXY_AUTH_TOKEN", None);
let _gateway_token_guard = set_env_var("GATEWAY_AUTH_TOKEN", Some("gateway-test-token"));
let secrets = test_secrets_store();
@@ -4162,9 +4162,9 @@ mod tests {
// sees a stable proxy URL/token configuration throughout the test.
let _env_guard = crate::config::helpers::lock_env();
let _exchange_url_guard =
set_env_var("IRONCLAW_OAUTH_EXCHANGE_URL", Some(&proxy.base_url()));
set_env_var("OPTIMCLAW_OAUTH_EXCHANGE_URL", Some(&proxy.base_url()));
let _proxy_auth_guard = set_env_var(
"IRONCLAW_OAUTH_PROXY_AUTH_TOKEN",
"OPTIMCLAW_OAUTH_PROXY_AUTH_TOKEN",
Some("shared-oauth-proxy-secret"),
);
let _gateway_token_guard = set_env_var("GATEWAY_AUTH_TOKEN", None);
+2 -2
View File
@@ -120,9 +120,9 @@ pub struct ApprovalRequest {
pub thread_id: Option<String>,
}
// --- App Event (re-exported from ironclaw_common) ---
// --- App Event (re-exported from optimclaw_common) ---
pub use ironclaw_common::{AppEvent, ToolDecisionDto};
pub use optimclaw_common::{AppEvent, ToolDecisionDto};
// --- Memory ---
+2 -2
View File
@@ -2,11 +2,11 @@
use crate::channels::web::types::{ToolCallInfo, TurnInfo};
pub use ironclaw_common::truncate_preview;
pub use optimclaw_common::truncate_preview;
/// Convert stored tool errors into plain text suitable for UI display.
pub fn tool_error_for_display(error: &str) -> String {
ironclaw_safety::SafetyLayer::unwrap_tool_output(error).unwrap_or_else(|| error.to_string())
optimclaw_safety::SafetyLayer::unwrap_tool_output(error).unwrap_or_else(|| error.to_string())
}
/// Parse tool call summary JSON objects into `ToolCallInfo` structs.
+2 -2
View File
@@ -13,7 +13,7 @@
//! Until `resolve()` falls back to settings (or the CLI writes `.env`),
//! an `enable`/`disable` command would silently fail to take effect.
//!
//! `status` (runtime health) requires connecting to a running IronClaw instance
//! `status` (runtime health) requires connecting to a running OptimClaw instance
//! via IPC or HTTP, which does not exist yet as a CLI control plane.
use std::path::Path;
@@ -211,7 +211,7 @@ async fn cmd_list(
println!("Use --verbose for details.");
println!();
println!("Note: enable/disable not yet available. Channel configuration is");
println!("managed via environment variables. See 'ironclaw onboard --channels-only'.");
println!("managed via environment variables. See 'optimclaw onboard --channels-only'.");
}
Ok(())
+2 -2
View File
@@ -2,7 +2,7 @@ use clap::{CommandFactory, Parser};
use clap_complete::{Shell, generate};
use std::io::{self, Write};
/// Generate shell completion scripts for ironclaw
/// Generate shell completion scripts for optimclaw
#[derive(Parser, Debug)]
pub struct Completion {
/// The shell to generate completions for
@@ -17,7 +17,7 @@ impl Completion {
if self.shell == Shell::Zsh {
// Generate to buffer so we can patch the compdef call.
// clap_complete emits bare `compdef _ironclaw ironclaw` which
// clap_complete emits bare `compdef _optimclaw optimclaw` which
// errors if sourced before compinit. Guard it so the script
// works in all sourcing contexts.
let mut buf = Vec::new();
+4 -4
View File
@@ -13,7 +13,7 @@ use crate::settings::Settings;
pub enum ConfigCommand {
/// Generate a default config.toml file
Init {
/// Output path (default: ~/.ironclaw/config.toml)
/// Output path (default: ~/.optimclaw/config.toml)
#[arg(short, long)]
output: Option<std::path::PathBuf>,
@@ -239,14 +239,14 @@ fn show_path(has_db: bool) -> anyhow::Result<()> {
}
println!(
"Env config: {}",
crate::bootstrap::ironclaw_env_path().display()
crate::bootstrap::optimclaw_env_path().display()
);
let toml_path = Settings::default_toml_path();
let toml_status = if toml_path.exists() {
"found"
} else {
"not found (run `ironclaw config init` to create)"
"not found (run `optimclaw config init` to create)"
};
println!(
"TOML config: {} ({})",
@@ -282,7 +282,7 @@ mod tests {
// Reset to default
settings.reset("agent.name").unwrap();
assert_eq!(settings.agent.name, "ironclaw");
assert_eq!(settings.agent.name, "optimclaw");
}
#[tokio::test]
+14 -14
View File
@@ -1,4 +1,4 @@
//! `ironclaw doctor` - active health diagnostics.
//! `optimclaw doctor` - active health diagnostics.
//!
//! Probes external dependencies and validates configuration to surface
//! problems before they bite during normal operation. Each check reports
@@ -6,14 +6,14 @@
use std::path::PathBuf;
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::cli::fmt;
use crate::settings::Settings;
/// Run all diagnostic checks and print results.
pub async fn run_doctor_command() -> anyhow::Result<()> {
println!();
println!(" {}IronClaw Doctor{}", fmt::bold(), fmt::reset());
println!(" {}OptimClaw Doctor{}", fmt::bold(), fmt::reset());
let mut passed = 0u32;
let mut failed = 0u32;
@@ -274,7 +274,7 @@ async fn check_nearai_session(settings: &Settings) -> CheckResult {
return CheckResult::Pass("API key configured".into());
}
return CheckResult::Fail(format!(
"session file not found at {}. Run `ironclaw onboard`",
"session file not found at {}. Run `optimclaw onboard`",
session_path.display()
));
}
@@ -376,7 +376,7 @@ async fn try_pg_connect() -> Result<(), String> {
// ── Workspace directory ─────────────────────────────────────
fn check_workspace_dir() -> CheckResult {
let dir = ironclaw_base_dir();
let dir = optimclaw_base_dir();
if dir.exists() {
if dir.is_dir() {
@@ -419,7 +419,7 @@ fn check_embeddings(settings: &Settings) -> CheckResult {
))
} else {
let hint = match config.provider.as_str() {
"nearai" => "run `ironclaw onboard` to create a session",
"nearai" => "run `optimclaw onboard` to create a session",
_ => "set OPENAI_API_KEY",
};
CheckResult::Fail(format!(
@@ -523,8 +523,8 @@ async fn check_mcp_config() -> CheckResult {
// ── Skills ──────────────────────────────────────────────────
async fn check_skills() -> CheckResult {
let user_dir = ironclaw_base_dir().join("skills");
let installed_dir = ironclaw_base_dir().join("installed_skills");
let user_dir = optimclaw_base_dir().join("skills");
let installed_dir = optimclaw_base_dir().join("installed_skills");
let mut registry = crate::skills::SkillRegistry::new(user_dir.clone());
registry = registry.with_installed_dir(installed_dir);
@@ -557,7 +557,7 @@ fn check_secrets(settings: &Settings) -> CheckResult {
}
}
crate::settings::KeySource::None => {
CheckResult::Skip("secrets not configured (run `ironclaw onboard`)".into())
CheckResult::Skip("secrets not configured (run `optimclaw onboard`)".into())
}
}
}
@@ -567,21 +567,21 @@ fn check_secrets(settings: &Settings) -> CheckResult {
fn check_service_installed() -> CheckResult {
if cfg!(target_os = "macos") {
let plist =
dirs::home_dir().map(|h| h.join("Library/LaunchAgents/com.ironclaw.daemon.plist"));
dirs::home_dir().map(|h| h.join("Library/LaunchAgents/com.optimclaw.daemon.plist"));
match plist {
Some(path) if path.exists() => {
CheckResult::Pass(format!("launchd plist installed ({})", path.display()))
}
Some(_) => CheckResult::Skip("not installed (run `ironclaw service install`)".into()),
Some(_) => CheckResult::Skip("not installed (run `optimclaw service install`)".into()),
None => CheckResult::Skip("cannot determine home directory".into()),
}
} else if cfg!(target_os = "linux") {
let unit = dirs::home_dir().map(|h| h.join(".config/systemd/user/ironclaw.service"));
let unit = dirs::home_dir().map(|h| h.join(".config/systemd/user/optimclaw.service"));
match unit {
Some(path) if path.exists() => {
CheckResult::Pass(format!("systemd unit installed ({})", path.display()))
}
Some(_) => CheckResult::Skip("not installed (run `ironclaw service install`)".into()),
Some(_) => CheckResult::Skip("not installed (run `optimclaw service install`)".into()),
None => CheckResult::Skip("cannot determine home directory".into()),
}
} else {
@@ -651,7 +651,7 @@ mod tests {
#[test]
fn check_binary_skips_nonexistent() {
match check_binary("__ironclaw_nonexistent_binary__", &["--version"]) {
match check_binary("__optimclaw_nonexistent_binary__", &["--version"]) {
CheckResult::Skip(_) => {}
other => panic!(
"expected Skip for nonexistent binary, got: {}",
+1 -1
View File
@@ -99,7 +99,7 @@ async fn discover_hooks(config: &crate::config::Config) -> Vec<HookInfo> {
///
/// Uses the same flat-file layout as the real WASM loaders:
/// ```text
/// ~/.ironclaw/tools/
/// ~/.optimclaw/tools/
/// ├── slack.wasm
/// ├── slack.capabilities.json <- hooks section parsed here
/// ├── github.wasm
+1 -1
View File
@@ -103,7 +103,7 @@ async fn run_import_openclaw(
}
Err(_) => {
return Err(anyhow::anyhow!(
"No secrets master key found. Set SECRETS_MASTER_KEY env var or run 'ironclaw onboard' first."
"No secrets master key found. Set SECRETS_MASTER_KEY env var or run 'optimclaw onboard' first."
));
}
}
+10 -10
View File
@@ -1,7 +1,7 @@
//! CLI command for viewing and managing gateway logs.
//!
//! Provides access to gateway logs through three mechanisms:
//! - Reading the gateway log file (`~/.ironclaw/gateway.log`)
//! - Reading the gateway log file (`~/.optimclaw/gateway.log`)
//! - Streaming live logs via the gateway's SSE endpoint (`/api/logs/events`)
//! - Getting/setting the runtime log level via `/api/logs/level`
@@ -14,7 +14,7 @@ use clap::Args;
#[derive(Args, Debug, Clone)]
#[command(
about = "View and manage gateway logs",
long_about = "Tail gateway logs, stream live output, or adjust log level.\nExamples:\n ironclaw logs # Show last 200 lines\n ironclaw logs --follow # Stream live logs via SSE\n ironclaw logs --limit 50 --json # Last 50 lines as JSON\n ironclaw logs --level # Show current log level\n ironclaw logs --level debug # Set log level to debug"
long_about = "Tail gateway logs, stream live output, or adjust log level.\nExamples:\n optimclaw logs # Show last 200 lines\n optimclaw logs --follow # Stream live logs via SSE\n optimclaw logs --limit 50 --json # Last 50 lines as JSON\n optimclaw logs --level # Show current log level\n optimclaw logs --level debug # Set log level to debug"
)]
pub struct LogsCommand {
/// Stream live logs from the running gateway via SSE.
@@ -84,18 +84,18 @@ pub async fn run_logs_command(cmd: LogsCommand, config_path: Option<&Path>) -> a
// ── Show log file ────────────────────────────────────────────────────────
/// Read the last N lines from `~/.ironclaw/gateway.log`.
/// Read the last N lines from `~/.optimclaw/gateway.log`.
///
/// Uses a reverse-scan strategy: seeks to the end of the file and reads
/// backwards in chunks to find the last `limit` newlines, so memory usage
/// is proportional to the output size, not the file size.
fn cmd_show(cmd: &LogsCommand) -> anyhow::Result<()> {
let log_path = crate::bootstrap::ironclaw_base_dir().join("gateway.log");
let log_path = crate::bootstrap::optimclaw_base_dir().join("gateway.log");
if !log_path.exists() {
anyhow::bail!(
"No gateway log file found at {}.\n\
The log file is created when the gateway runs in background mode \
(e.g. `ironclaw gateway start`).",
(e.g. `optimclaw gateway start`).",
log_path.display()
);
}
@@ -197,7 +197,7 @@ async fn cmd_follow(cmd: &LogsCommand, params: &GatewayParams) -> anyhow::Result
.map_err(|e| {
anyhow::anyhow!(
"Failed to connect to gateway at {url}: {e}\n\
Is the gateway running? Try `ironclaw gateway status`."
Is the gateway running? Try `optimclaw gateway status`."
)
})?;
@@ -264,7 +264,7 @@ async fn cmd_get_level(cmd: &LogsCommand, params: &GatewayParams) -> anyhow::Res
.map_err(|e| {
anyhow::anyhow!(
"Failed to connect to gateway at {url}: {e}\n\
Is the gateway running? Try `ironclaw gateway status`."
Is the gateway running? Try `optimclaw gateway status`."
)
})?;
@@ -330,7 +330,7 @@ async fn cmd_set_level(
.map_err(|e| {
anyhow::anyhow!(
"Failed to connect to gateway at {url}: {e}\n\
Is the gateway running? Try `ironclaw gateway status`."
Is the gateway running? Try `optimclaw gateway status`."
)
})?;
@@ -412,7 +412,7 @@ async fn resolve_gateway_params(
/// propagated — the user asked for a specific file and deserves a clear
/// failure when it is missing, unreadable, or malformed. When no path
/// was given we fall back to env-only resolution and silently return
/// `None` on failure so that `ironclaw logs` works without any config.
/// `None` on failure so that `optimclaw logs` works without any config.
async fn load_gateway_config(
config_path: Option<&Path>,
) -> anyhow::Result<Option<crate::config::GatewayConfig>> {
@@ -512,7 +512,7 @@ mod tests {
fn test_print_log_entry_json() {
let entry = serde_json::json!({
"level": "INFO",
"target": "ironclaw::agent",
"target": "optimclaw::agent",
"message": "test message",
"timestamp": "2024-01-15T10:30:00.000Z"
});
+7 -7
View File
@@ -271,7 +271,7 @@ async fn add_server(args: McpAddArgs) -> anyhow::Result<()> {
if requires_auth {
println!();
println!(" Run 'ironclaw mcp auth {}' to authenticate.", name);
println!(" Run 'optimclaw mcp auth {}' to authenticate.", name);
}
println!();
@@ -305,7 +305,7 @@ async fn list_servers(verbose: bool) -> anyhow::Result<()> {
println!(" No MCP servers configured.");
println!();
println!(" Add a server with:");
println!(" ironclaw mcp add <name> <url> [--client-id <id>]");
println!(" optimclaw mcp add <name> <url> [--client-id <id>]");
println!();
return Ok(());
}
@@ -455,9 +455,9 @@ async fn auth_server(name: String, user_id: String) -> anyhow::Result<()> {
println!(" The server may require a different authentication method,");
println!(" or you may need to configure OAuth manually:");
println!();
println!(" ironclaw mcp remove {}", name);
println!(" optimclaw mcp remove {}", name);
println!(
" ironclaw mcp add {} {} --client-id YOUR_CLIENT_ID",
" optimclaw mcp add {} {} --client-id YOUR_CLIENT_ID",
name, server.url
);
println!();
@@ -500,7 +500,7 @@ async fn test_server(name: String, user_id: String) -> anyhow::Result<()> {
// OAuth configured but no tokens - need to authenticate
println!();
println!(
" ✗ Not authenticated. Run 'ironclaw mcp auth {}' first.",
" ✗ Not authenticated. Run 'optimclaw mcp auth {}' first.",
name
);
println!();
@@ -561,12 +561,12 @@ async fn test_server(name: String, user_id: String) -> anyhow::Result<()> {
println!(
" ✗ Authentication failed (token may be expired). Try re-authenticating:"
);
println!(" ironclaw mcp auth {}", name);
println!(" optimclaw mcp auth {}", name);
} else {
// No tokens - server requires auth
println!(" ✗ Server requires authentication.");
println!();
println!(" Run 'ironclaw mcp auth {}' to authenticate.", name);
println!(" Run 'optimclaw mcp auth {}' to authenticate.", name);
}
} else {
println!(" ✗ Connection failed: {}", e);
+24 -24
View File
@@ -60,12 +60,12 @@ use std::sync::Arc;
use clap::{ColorChoice, Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(name = "ironclaw")]
#[command(name = "optimclaw")]
#[command(
about = "Secure personal AI assistant that protects your data and expands its capabilities"
)]
#[command(
long_about = "IronClaw is a secure AI assistant. Use 'ironclaw <subcommand> --help' for details.\nExamples:\n ironclaw run # Start the agent\n ironclaw config list # List configs"
long_about = "OptimClaw is a secure AI assistant. Use 'optimclaw <subcommand> --help' for details.\nExamples:\n optimclaw run # Start the agent\n optimclaw config list # List configs"
)]
#[command(version)]
#[command(color = ColorChoice::Auto)] // Enable auto-color for help (if the terminal supports it)
@@ -99,14 +99,14 @@ pub enum Command {
/// Run the agent (default if no subcommand given)
#[command(
about = "Run the AI agent",
long_about = "Starts the IronClaw agent in default mode.\nExample: ironclaw run"
long_about = "Starts the OptimClaw agent in default mode.\nExample: optimclaw run"
)]
Run,
/// Interactive onboarding wizard
#[command(
about = "Run interactive setup wizard",
long_about = "Guides through initial configuration.\nExamples:\n ironclaw onboard --skip-auth # Skip auth step\n ironclaw onboard --channels-only # Reconfigure channels\n ironclaw onboard --provider-only # Change LLM provider and model"
long_about = "Guides through initial configuration.\nExamples:\n optimclaw onboard --skip-auth # Skip auth step\n optimclaw onboard --channels-only # Reconfigure channels\n optimclaw onboard --provider-only # Change LLM provider and model"
)]
Onboard {
/// Skip authentication (use existing session)
@@ -134,7 +134,7 @@ pub enum Command {
#[command(
subcommand,
about = "Manage app configs",
long_about = "Commands for listing, getting, and setting configurations.\nExample: ironclaw config list"
long_about = "Commands for listing, getting, and setting configurations.\nExample: optimclaw config list"
)]
Config(ConfigCommand),
@@ -142,7 +142,7 @@ pub enum Command {
#[command(
subcommand,
about = "Manage WASM tools",
long_about = "Install, list, or remove WASM-based tools.\nExample: ironclaw tool install mytool.wasm"
long_about = "Install, list, or remove WASM-based tools.\nExample: optimclaw tool install mytool.wasm"
)]
Tool(ToolCommand),
@@ -150,7 +150,7 @@ pub enum Command {
#[command(
subcommand,
about = "Browse/install extensions",
long_about = "Interact with extension registry.\nExample: ironclaw registry list"
long_about = "Interact with extension registry.\nExample: optimclaw registry list"
)]
Registry(RegistryCommand),
@@ -158,7 +158,7 @@ pub enum Command {
#[command(
subcommand,
about = "Manage channels",
long_about = "List configured messaging channels.\nExamples:\n ironclaw channels list\n ironclaw channels list --verbose\n ironclaw channels list --json"
long_about = "List configured messaging channels.\nExamples:\n optimclaw channels list\n optimclaw channels list --verbose\n optimclaw channels list --json"
)]
Channels(ChannelsCommand),
@@ -167,7 +167,7 @@ pub enum Command {
subcommand,
alias = "cron",
about = "Manage routines",
long_about = "List, create, edit, enable/disable, delete, and view history of routines.\nExamples:\n ironclaw routines list\n ironclaw routines create --name daily-digest --schedule '0 0 9 * * *' --prompt 'Summarize today'"
long_about = "List, create, edit, enable/disable, delete, and view history of routines.\nExamples:\n optimclaw routines list\n optimclaw routines create --name daily-digest --schedule '0 0 9 * * *' --prompt 'Summarize today'"
)]
Routines(RoutinesCommand),
@@ -175,7 +175,7 @@ pub enum Command {
#[command(
subcommand,
about = "Manage MCP servers",
long_about = "Add, auth, list, or test MCP servers.\nExample: ironclaw mcp add notion https://mcp.notion.com"
long_about = "Add, auth, list, or test MCP servers.\nExample: optimclaw mcp add notion https://mcp.notion.com"
)]
Mcp(Box<McpCommand>),
@@ -183,7 +183,7 @@ pub enum Command {
#[command(
subcommand,
about = "Manage workspace memory",
long_about = "Search, read, or write to memory.\nExample: ironclaw memory search 'query'"
long_about = "Search, read, or write to memory.\nExample: optimclaw memory search 'query'"
)]
Memory(MemoryCommand),
@@ -191,7 +191,7 @@ pub enum Command {
#[command(
subcommand,
about = "Manage DM pairing",
long_about = "Approve or manage pairing requests.\nExamples:\n ironclaw pairing list telegram\n ironclaw pairing approve telegram ABC12345"
long_about = "Approve or manage pairing requests.\nExamples:\n optimclaw pairing list telegram\n optimclaw pairing approve telegram ABC12345"
)]
Pairing(PairingCommand),
@@ -199,7 +199,7 @@ pub enum Command {
#[command(
subcommand,
about = "Manage OS service",
long_about = "Install, start, or stop service.\nExample: ironclaw service install"
long_about = "Install, start, or stop service.\nExample: optimclaw service install"
)]
Service(ServiceCommand),
@@ -207,7 +207,7 @@ pub enum Command {
#[command(
subcommand,
about = "Manage skills",
long_about = "List, search, and inspect SKILL.md-based skills.\nExamples:\n ironclaw skills list\n ironclaw skills search 'writing'\n ironclaw skills info my-skill"
long_about = "List, search, and inspect SKILL.md-based skills.\nExamples:\n optimclaw skills list\n optimclaw skills search 'writing'\n optimclaw skills info my-skill"
)]
Skills(SkillsCommand),
@@ -215,7 +215,7 @@ pub enum Command {
#[command(
subcommand,
about = "Manage lifecycle hooks",
long_about = "List and inspect lifecycle hooks (bundled, plugin, workspace).\nExamples:\n ironclaw hooks list\n ironclaw hooks list --verbose\n ironclaw hooks list --json"
long_about = "List and inspect lifecycle hooks (bundled, plugin, workspace).\nExamples:\n optimclaw hooks list\n optimclaw hooks list --verbose\n optimclaw hooks list --json"
)]
Hooks(HooksCommand),
@@ -223,35 +223,35 @@ pub enum Command {
#[command(
subcommand,
about = "Manage LLM providers and models",
long_about = "List providers, view current configuration, and set active provider/model.\nExamples:\n ironclaw models list\n ironclaw models list openai --verbose\n ironclaw models status\n ironclaw models set gpt-4o\n ironclaw models set-provider anthropic --model claude-sonnet-4-6-20250514"
long_about = "List providers, view current configuration, and set active provider/model.\nExamples:\n optimclaw models list\n optimclaw models list openai --verbose\n optimclaw models status\n optimclaw models set gpt-4o\n optimclaw models set-provider anthropic --model claude-sonnet-4-6-20250514"
)]
Models(ModelsCommand),
/// Probe external dependencies and validate configuration
#[command(
about = "Run diagnostics",
long_about = "Checks dependencies and config validity.\nExample: ironclaw doctor"
long_about = "Checks dependencies and config validity.\nExample: optimclaw doctor"
)]
Doctor,
/// View and manage gateway logs
#[command(
about = "View and manage gateway logs",
long_about = "Tail gateway logs, stream live output, or adjust log level.\nExamples:\n ironclaw logs # Show last 200 lines from gateway.log\n ironclaw logs --follow # Stream live logs via SSE\n ironclaw logs --level # Show current log level\n ironclaw logs --level debug # Set log level to debug"
long_about = "Tail gateway logs, stream live output, or adjust log level.\nExamples:\n optimclaw logs # Show last 200 lines from gateway.log\n optimclaw logs --follow # Stream live logs via SSE\n optimclaw logs --level # Show current log level\n optimclaw logs --level debug # Set log level to debug"
)]
Logs(LogsCommand),
/// Show system health and diagnostics
#[command(
about = "Show system status",
long_about = "Displays health and diagnostics info.\nExample: ironclaw status"
long_about = "Displays health and diagnostics info.\nExample: optimclaw status"
)]
Status,
/// Generate shell completion scripts
#[command(
about = "Generate completions",
long_about = "Generates shell completion scripts.\nExample: ironclaw completion --shell bash > ironclaw.bash"
long_about = "Generates shell completion scripts.\nExample: optimclaw completion --shell bash > optimclaw.bash"
)]
Completion(Completion),
@@ -260,14 +260,14 @@ pub enum Command {
#[command(
subcommand,
about = "Import from other AI systems",
long_about = "Migrate data from other AI assistants like OpenClaw.\nExample: ironclaw import openclaw"
long_about = "Migrate data from other AI assistants like OpenClaw.\nExample: optimclaw import openclaw"
)]
Import(ImportCommand),
/// Authenticate with a provider (re-login)
#[command(
about = "Authenticate with a provider",
long_about = "Re-authenticate with an LLM provider.\nExample: ironclaw login --openai-codex"
long_about = "Re-authenticate with an LLM provider.\nExample: optimclaw login --openai-codex"
)]
Login {
/// Authenticate with OpenAI Codex (ChatGPT subscription)
@@ -330,7 +330,7 @@ pub async fn init_secrets_store()
let config = crate::config::Config::from_env().await?;
let master_key = config.secrets.master_key().ok_or_else(|| {
anyhow::anyhow!(
"SECRETS_MASTER_KEY not set. Run 'ironclaw onboard' first or set it in .env"
"SECRETS_MASTER_KEY not set. Run 'optimclaw onboard' first or set it in .env"
)
})?;
@@ -352,7 +352,7 @@ pub async fn run_routines_cli(
.await
.map_err(|e| anyhow::anyhow!("{e:#}"))?;
let user_id = std::env::var("IRONCLAW_OWNER_ID").unwrap_or_else(|_| "default".to_string());
let user_id = std::env::var("OPTIMCLAW_OWNER_ID").unwrap_or_else(|_| "default".to_string());
run_routines_command(routines_cmd.clone(), db, &user_id).await
}
+4 -4
View File
@@ -2,7 +2,7 @@
//!
//! Provides subcommands for listing providers, viewing current model
//! configuration, and setting the active provider/model. Settings are
//! persisted to both `config.toml` and `~/.ironclaw/.env` so changes
//! persisted to both `config.toml` and `~/.optimclaw/.env` so changes
//! take effect immediately (no DB connection required).
use clap::Subcommand;
@@ -147,7 +147,7 @@ fn save_settings(settings: &Settings, config_path: Option<&Path>) -> anyhow::Res
}
fn config_toml_path() -> std::path::PathBuf {
crate::bootstrap::ironclaw_base_dir().join("config.toml")
crate::bootstrap::optimclaw_base_dir().join("config.toml")
}
/// Try to fetch the live model list from a provider.
@@ -228,7 +228,7 @@ fn print_model_list(models: &Option<Vec<String>>, active_model: Option<&String>)
}
}
/// Also update `~/.ironclaw/.env` so changes take effect immediately.
/// Also update `~/.optimclaw/.env` so changes take effect immediately.
///
/// Skipped when `config_path` is `Some` (custom `--config`), because the user
/// is explicitly targeting a different config file and we must not pollute the
@@ -818,7 +818,7 @@ mod tests {
// With a custom config path, sync_to_dotenv should be a no-op
// (it returns early when config_path is Some).
// We verify by checking that cmd_set_provider succeeds without
// trying to write to the default ~/.ironclaw/.env.
// trying to write to the default ~/.optimclaw/.env.
cmd_set_provider("groq", None, Some(&toml_path)).expect("set provider with custom config");
let settings = Settings::load_toml(&toml_path)
+52 -52
View File
@@ -31,11 +31,11 @@ pub struct OAuthCredentials {
/// Google OAuth "Desktop App" credentials, shared across all Google tools.
/// Compile-time env vars override the hardcoded defaults below.
const GOOGLE_CLIENT_ID: &str = match option_env!("IRONCLAW_GOOGLE_CLIENT_ID") {
const GOOGLE_CLIENT_ID: &str = match option_env!("OPTIMCLAW_GOOGLE_CLIENT_ID") {
Some(v) => v,
None => "564604149681-efo25d43rs85v0tibdepsmdv5dsrhhr0.apps.googleusercontent.com",
};
const GOOGLE_CLIENT_SECRET: &str = match option_env!("IRONCLAW_GOOGLE_CLIENT_SECRET") {
const GOOGLE_CLIENT_SECRET: &str = match option_env!("OPTIMCLAW_GOOGLE_CLIENT_SECRET") {
Some(v) => v,
None => "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",
};
@@ -57,14 +57,14 @@ pub fn builtin_credentials(secret_name: &str) -> Option<OAuthCredentials> {
/// Returns the compile-time override env var name, if this provider supports one.
pub fn builtin_client_id_override_env(secret_name: &str) -> Option<&'static str> {
match secret_name {
"google_oauth_token" => Some("IRONCLAW_GOOGLE_CLIENT_ID"),
"google_oauth_token" => Some("OPTIMCLAW_GOOGLE_CLIENT_ID"),
_ => None,
}
}
/// Suppress the baked-in desktop OAuth client secret when a hosted proxy is configured.
///
/// In hosted deployments, IronClaw may resolve the platform Google client ID from
/// In hosted deployments, OptimClaw may resolve the platform Google client ID from
/// environment variables while still falling back to the baked-in desktop secret.
/// That client_id/client_secret mismatch breaks Google token exchange and refresh.
///
@@ -514,11 +514,11 @@ pub fn new_pending_oauth_registry() -> PendingOAuthRegistry {
/// Returns `true` if OAuth callbacks should be routed through the web gateway
/// instead of the local TCP listener.
///
/// This is the case when `IRONCLAW_OAUTH_CALLBACK_URL` is set to a non-loopback
/// This is the case when `OPTIMCLAW_OAUTH_CALLBACK_URL` is set to a non-loopback
/// URL, meaning the user's browser will redirect to a hosted gateway rather than
/// localhost.
pub fn use_gateway_callback() -> bool {
crate::config::helpers::env_or_override("IRONCLAW_OAUTH_CALLBACK_URL")
crate::config::helpers::env_or_override("OPTIMCLAW_OAUTH_CALLBACK_URL")
.map(|raw| {
url::Url::parse(&raw)
.ok()
@@ -531,7 +531,7 @@ pub fn use_gateway_callback() -> bool {
/// Returns the configured OAuth token-exchange proxy URL, if any.
pub fn exchange_proxy_url() -> Option<String> {
crate::config::helpers::env_or_override("IRONCLAW_OAUTH_EXCHANGE_URL")
crate::config::helpers::env_or_override("OPTIMCLAW_OAUTH_EXCHANGE_URL")
.map(|url| url.trim().to_string())
.filter(|url| !url.is_empty())
}
@@ -539,7 +539,7 @@ pub fn exchange_proxy_url() -> Option<String> {
/// Returns the configured OAuth proxy auth token, if any.
///
/// New hosted infra can inject a dedicated shared proxy secret via
/// `IRONCLAW_OAUTH_PROXY_AUTH_TOKEN`. Existing hosted instances continue to
/// `OPTIMCLAW_OAUTH_PROXY_AUTH_TOKEN`. Existing hosted instances continue to
/// work by falling back to `GATEWAY_AUTH_TOKEN`.
pub fn oauth_proxy_auth_token() -> Option<String> {
fn normalized_env_value(key: &str) -> Option<String> {
@@ -548,7 +548,7 @@ pub fn oauth_proxy_auth_token() -> Option<String> {
.filter(|value| !value.is_empty())
}
normalized_env_value("IRONCLAW_OAUTH_PROXY_AUTH_TOKEN")
normalized_env_value("OPTIMCLAW_OAUTH_PROXY_AUTH_TOKEN")
.or_else(|| normalized_env_value("GATEWAY_AUTH_TOKEN"))
}
@@ -621,7 +621,7 @@ struct HostedOAuthStatePayload {
}
fn current_instance_name() -> Option<String> {
crate::config::helpers::env_or_override("IRONCLAW_INSTANCE_NAME")
crate::config::helpers::env_or_override("OPTIMCLAW_INSTANCE_NAME")
.or_else(|| crate::config::helpers::env_or_override("OPENCLAW_INSTANCE_NAME"))
.filter(|v| !v.is_empty())
}
@@ -634,7 +634,7 @@ fn hosted_state_checksum(payload_bytes: &[u8]) -> String {
/// Build a versioned hosted OAuth state envelope.
///
/// The encoded value is opaque to providers and can be decoded by both
/// IronClaw and the external auth proxy for routing and callback lookup.
/// OptimClaw and the external auth proxy for routing and callback lookup.
pub fn encode_hosted_oauth_state(flow_id: &str, instance_name: Option<&str>) -> String {
let payload = HostedOAuthStatePayload {
flow_id: flow_id.to_string(),
@@ -1351,11 +1351,11 @@ mod tests {
fn test_callback_host_env_override() {
let _guard = lock_env();
let original_host = std::env::var("OAUTH_CALLBACK_HOST").ok();
let original_url = std::env::var("IRONCLAW_OAUTH_CALLBACK_URL").ok();
let original_url = std::env::var("OPTIMCLAW_OAUTH_CALLBACK_URL").ok();
// SAFETY: Under ENV_MUTEX, no concurrent env access.
unsafe {
std::env::set_var("OAUTH_CALLBACK_HOST", "203.0.113.10");
std::env::remove_var("IRONCLAW_OAUTH_CALLBACK_URL");
std::env::remove_var("OPTIMCLAW_OAUTH_CALLBACK_URL");
}
assert_eq!(callback_host(), "203.0.113.10");
// callback_url() fallback should incorporate the custom host
@@ -1369,7 +1369,7 @@ mod tests {
std::env::remove_var("OAUTH_CALLBACK_HOST");
}
if let Some(val) = original_url {
std::env::set_var("IRONCLAW_OAUTH_CALLBACK_URL", val);
std::env::set_var("OPTIMCLAW_OAUTH_CALLBACK_URL", val);
}
}
}
@@ -1378,11 +1378,11 @@ mod tests {
fn test_callback_url_default() {
let _guard = lock_env();
// Clear both env vars to test default behavior
let original_url = std::env::var("IRONCLAW_OAUTH_CALLBACK_URL").ok();
let original_url = std::env::var("OPTIMCLAW_OAUTH_CALLBACK_URL").ok();
let original_host = std::env::var("OAUTH_CALLBACK_HOST").ok();
// SAFETY: Under ENV_MUTEX, no concurrent env access.
unsafe {
std::env::remove_var("IRONCLAW_OAUTH_CALLBACK_URL");
std::env::remove_var("OPTIMCLAW_OAUTH_CALLBACK_URL");
std::env::remove_var("OAUTH_CALLBACK_HOST");
}
let url = callback_url();
@@ -1390,7 +1390,7 @@ mod tests {
// Restore
unsafe {
if let Some(val) = original_url {
std::env::set_var("IRONCLAW_OAUTH_CALLBACK_URL", val);
std::env::set_var("OPTIMCLAW_OAUTH_CALLBACK_URL", val);
}
if let Some(val) = original_host {
std::env::set_var("OAUTH_CALLBACK_HOST", val);
@@ -1401,11 +1401,11 @@ mod tests {
#[test]
fn test_callback_url_env_override() {
let _guard = lock_env();
let original = std::env::var("IRONCLAW_OAUTH_CALLBACK_URL").ok();
let original = std::env::var("OPTIMCLAW_OAUTH_CALLBACK_URL").ok();
// SAFETY: Under ENV_MUTEX, no concurrent env access.
unsafe {
std::env::set_var(
"IRONCLAW_OAUTH_CALLBACK_URL",
"OPTIMCLAW_OAUTH_CALLBACK_URL",
"https://myserver.example.com:9876",
);
}
@@ -1414,9 +1414,9 @@ mod tests {
// Restore
unsafe {
if let Some(val) = original {
std::env::set_var("IRONCLAW_OAUTH_CALLBACK_URL", val);
std::env::set_var("OPTIMCLAW_OAUTH_CALLBACK_URL", val);
} else {
std::env::remove_var("IRONCLAW_OAUTH_CALLBACK_URL");
std::env::remove_var("OPTIMCLAW_OAUTH_CALLBACK_URL");
}
}
}
@@ -1440,7 +1440,7 @@ mod tests {
let html = landing_html("Google", true);
assert!(html.contains("Google Connected"));
assert!(html.contains("charset"));
assert!(html.contains("IronClaw"));
assert!(html.contains("OptimClaw"));
assert!(html.contains("#22c55e")); // green accent
assert!(!html.contains("Failed"));
}
@@ -1457,7 +1457,7 @@ mod tests {
let html = landing_html("Notion", false);
assert!(html.contains("Authorization Failed"));
assert!(html.contains("charset"));
assert!(html.contains("IronClaw"));
assert!(html.contains("OptimClaw"));
assert!(html.contains("#ef4444")); // red accent
assert!(!html.contains("Connected"));
}
@@ -1566,15 +1566,15 @@ mod tests {
#[test]
fn test_use_gateway_callback_false_by_default() {
let _guard = lock_env();
let original = std::env::var("IRONCLAW_OAUTH_CALLBACK_URL").ok();
let original = std::env::var("OPTIMCLAW_OAUTH_CALLBACK_URL").ok();
// SAFETY: Under ENV_MUTEX, no concurrent env access.
unsafe {
std::env::remove_var("IRONCLAW_OAUTH_CALLBACK_URL");
std::env::remove_var("OPTIMCLAW_OAUTH_CALLBACK_URL");
}
assert!(!crate::cli::oauth_defaults::use_gateway_callback());
unsafe {
if let Some(val) = original {
std::env::set_var("IRONCLAW_OAUTH_CALLBACK_URL", val);
std::env::set_var("OPTIMCLAW_OAUTH_CALLBACK_URL", val);
}
}
}
@@ -1582,20 +1582,20 @@ mod tests {
#[test]
fn test_use_gateway_callback_true_for_hosted() {
let _guard = lock_env();
let original = std::env::var("IRONCLAW_OAUTH_CALLBACK_URL").ok();
let original = std::env::var("OPTIMCLAW_OAUTH_CALLBACK_URL").ok();
// SAFETY: Under ENV_MUTEX, no concurrent env access.
unsafe {
std::env::set_var(
"IRONCLAW_OAUTH_CALLBACK_URL",
"OPTIMCLAW_OAUTH_CALLBACK_URL",
"https://kind-deer.agent1.near.ai",
);
}
assert!(crate::cli::oauth_defaults::use_gateway_callback());
unsafe {
if let Some(val) = original {
std::env::set_var("IRONCLAW_OAUTH_CALLBACK_URL", val);
std::env::set_var("OPTIMCLAW_OAUTH_CALLBACK_URL", val);
} else {
std::env::remove_var("IRONCLAW_OAUTH_CALLBACK_URL");
std::env::remove_var("OPTIMCLAW_OAUTH_CALLBACK_URL");
}
}
}
@@ -1603,17 +1603,17 @@ mod tests {
#[test]
fn test_use_gateway_callback_false_for_localhost() {
let _guard = lock_env();
let original = std::env::var("IRONCLAW_OAUTH_CALLBACK_URL").ok();
let original = std::env::var("OPTIMCLAW_OAUTH_CALLBACK_URL").ok();
// SAFETY: Under ENV_MUTEX, no concurrent env access.
unsafe {
std::env::set_var("IRONCLAW_OAUTH_CALLBACK_URL", "http://127.0.0.1:3001");
std::env::set_var("OPTIMCLAW_OAUTH_CALLBACK_URL", "http://127.0.0.1:3001");
}
assert!(!crate::cli::oauth_defaults::use_gateway_callback());
unsafe {
if let Some(val) = original {
std::env::set_var("IRONCLAW_OAUTH_CALLBACK_URL", val);
std::env::set_var("OPTIMCLAW_OAUTH_CALLBACK_URL", val);
} else {
std::env::remove_var("IRONCLAW_OAUTH_CALLBACK_URL");
std::env::remove_var("OPTIMCLAW_OAUTH_CALLBACK_URL");
}
}
}
@@ -1621,17 +1621,17 @@ mod tests {
#[test]
fn test_use_gateway_callback_false_for_empty() {
let _guard = lock_env();
let original = std::env::var("IRONCLAW_OAUTH_CALLBACK_URL").ok();
let original = std::env::var("OPTIMCLAW_OAUTH_CALLBACK_URL").ok();
// SAFETY: Under ENV_MUTEX, no concurrent env access.
unsafe {
std::env::set_var("IRONCLAW_OAUTH_CALLBACK_URL", "");
std::env::set_var("OPTIMCLAW_OAUTH_CALLBACK_URL", "");
}
assert!(!crate::cli::oauth_defaults::use_gateway_callback());
unsafe {
if let Some(val) = original {
std::env::set_var("IRONCLAW_OAUTH_CALLBACK_URL", val);
std::env::set_var("OPTIMCLAW_OAUTH_CALLBACK_URL", val);
} else {
std::env::remove_var("IRONCLAW_OAUTH_CALLBACK_URL");
std::env::remove_var("OPTIMCLAW_OAUTH_CALLBACK_URL");
}
}
}
@@ -1641,10 +1641,10 @@ mod tests {
use crate::cli::oauth_defaults::{build_platform_state, decode_hosted_oauth_state};
let _guard = lock_env();
let original = std::env::var("IRONCLAW_INSTANCE_NAME").ok();
let original = std::env::var("OPTIMCLAW_INSTANCE_NAME").ok();
// SAFETY: Under ENV_MUTEX, no concurrent env access.
unsafe {
std::env::set_var("IRONCLAW_INSTANCE_NAME", "kind-deer");
std::env::set_var("OPTIMCLAW_INSTANCE_NAME", "kind-deer");
}
let encoded = build_platform_state("abc123");
let decoded = decode_hosted_oauth_state(&encoded).expect("decode hosted state");
@@ -1653,9 +1653,9 @@ mod tests {
assert!(!decoded.is_legacy);
unsafe {
if let Some(val) = original {
std::env::set_var("IRONCLAW_INSTANCE_NAME", val);
std::env::set_var("OPTIMCLAW_INSTANCE_NAME", val);
} else {
std::env::remove_var("IRONCLAW_INSTANCE_NAME");
std::env::remove_var("OPTIMCLAW_INSTANCE_NAME");
}
}
}
@@ -1665,11 +1665,11 @@ mod tests {
use crate::cli::oauth_defaults::{build_platform_state, decode_hosted_oauth_state};
let _guard = lock_env();
let original = std::env::var("IRONCLAW_INSTANCE_NAME").ok();
let original = std::env::var("OPTIMCLAW_INSTANCE_NAME").ok();
let original_oc = std::env::var("OPENCLAW_INSTANCE_NAME").ok();
// SAFETY: Under ENV_MUTEX, no concurrent env access.
unsafe {
std::env::remove_var("IRONCLAW_INSTANCE_NAME");
std::env::remove_var("OPTIMCLAW_INSTANCE_NAME");
std::env::remove_var("OPENCLAW_INSTANCE_NAME");
}
let encoded = build_platform_state("abc123");
@@ -1679,7 +1679,7 @@ mod tests {
assert!(!decoded.is_legacy);
unsafe {
if let Some(val) = original {
std::env::set_var("IRONCLAW_INSTANCE_NAME", val);
std::env::set_var("OPTIMCLAW_INSTANCE_NAME", val);
}
if let Some(val) = original_oc {
std::env::set_var("OPENCLAW_INSTANCE_NAME", val);
@@ -1692,11 +1692,11 @@ mod tests {
use crate::cli::oauth_defaults::{build_platform_state, decode_hosted_oauth_state};
let _guard = lock_env();
let original_ic = std::env::var("IRONCLAW_INSTANCE_NAME").ok();
let original_ic = std::env::var("OPTIMCLAW_INSTANCE_NAME").ok();
let original_oc = std::env::var("OPENCLAW_INSTANCE_NAME").ok();
// SAFETY: Under ENV_MUTEX, no concurrent env access.
unsafe {
std::env::remove_var("IRONCLAW_INSTANCE_NAME");
std::env::remove_var("OPTIMCLAW_INSTANCE_NAME");
std::env::set_var("OPENCLAW_INSTANCE_NAME", "quiet-lion");
}
let encoded = build_platform_state("xyz789");
@@ -1706,7 +1706,7 @@ mod tests {
assert!(!decoded.is_legacy);
unsafe {
if let Some(val) = original_ic {
std::env::set_var("IRONCLAW_INSTANCE_NAME", val);
std::env::set_var("OPTIMCLAW_INSTANCE_NAME", val);
}
if let Some(val) = original_oc {
std::env::set_var("OPENCLAW_INSTANCE_NAME", val);
@@ -1720,7 +1720,7 @@ mod tests {
fn test_oauth_proxy_auth_token_prefers_dedicated_env() {
let _guard = lock_env();
let _proxy_guard = set_env_var(
"IRONCLAW_OAUTH_PROXY_AUTH_TOKEN",
"OPTIMCLAW_OAUTH_PROXY_AUTH_TOKEN",
Some("shared-proxy-secret"),
);
let _gateway_guard = set_env_var("GATEWAY_AUTH_TOKEN", Some("gateway-token"));
@@ -1734,7 +1734,7 @@ mod tests {
#[test]
fn test_oauth_proxy_auth_token_falls_back_to_gateway_token() {
let _guard = lock_env();
let _proxy_guard = set_env_var("IRONCLAW_OAUTH_PROXY_AUTH_TOKEN", None);
let _proxy_guard = set_env_var("OPTIMCLAW_OAUTH_PROXY_AUTH_TOKEN", None);
let _gateway_guard = set_env_var("GATEWAY_AUTH_TOKEN", Some("gateway-token"));
assert_eq!(
@@ -1746,7 +1746,7 @@ mod tests {
#[test]
fn test_oauth_proxy_auth_token_whitespace_dedicated_env_falls_back_to_gateway_token() {
let _guard = lock_env();
let _proxy_guard = set_env_var("IRONCLAW_OAUTH_PROXY_AUTH_TOKEN", Some(" "));
let _proxy_guard = set_env_var("OPTIMCLAW_OAUTH_PROXY_AUTH_TOKEN", Some(" "));
let _gateway_guard = set_env_var("GATEWAY_AUTH_TOKEN", Some("gateway-token"));
assert_eq!(
@@ -1758,7 +1758,7 @@ mod tests {
#[test]
fn test_oauth_proxy_auth_token_returns_none_when_unset() {
let _guard = lock_env();
let _proxy_guard = set_env_var("IRONCLAW_OAUTH_PROXY_AUTH_TOKEN", None);
let _proxy_guard = set_env_var("OPTIMCLAW_OAUTH_PROXY_AUTH_TOKEN", None);
let _gateway_guard = set_env_var("GATEWAY_AUTH_TOKEN", None);
assert_eq!(crate::cli::oauth_defaults::oauth_proxy_auth_token(), None);
+2 -2
View File
@@ -144,7 +144,7 @@ fn cmd_list(
let bundle_names = catalog.bundle_names();
if !bundle_names.is_empty() {
println!("\nBundles available: {}", bundle_names.join(", "));
println!("Use `ironclaw registry info <bundle>` for details.");
println!("Use `optimclaw registry info <bundle>` for details.");
}
Ok(())
@@ -304,7 +304,7 @@ async fn cmd_install(
&& auth.method.as_deref() != Some("none")
{
println!(
"\nNext step: authenticate with `ironclaw tool auth {}`",
"\nNext step: authenticate with `optimclaw tool auth {}`",
manifest.name
);
if let Some(url) = &auth.setup_url {
+1 -1
View File
@@ -1,4 +1,4 @@
//! `ironclaw routines` — manage scheduled routines from the CLI.
//! `optimclaw routines` — manage scheduled routines from the CLI.
//!
//! Provides subcommands for listing, creating, editing, enabling/disabling,
//! deleting, and viewing run history of routines without starting the full agent.
+1 -1
View File
@@ -1,4 +1,4 @@
//! CLI subcommand definitions for `ironclaw service`.
//! CLI subcommand definitions for `optimclaw service`.
use clap::Subcommand;
+3 -3
View File
@@ -121,7 +121,7 @@ async fn cmd_list(config: &SkillsConfig, verbose: bool, json: bool) -> anyhow::R
println!(" User: {}", config.local_dir.display());
println!(" Installed: {}", config.installed_dir.display());
println!();
println!("Use 'ironclaw skills search <query>' to find skills on ClawHub.");
println!("Use 'optimclaw skills search <query>' to find skills on ClawHub.");
return Ok(());
}
@@ -157,7 +157,7 @@ async fn cmd_list(config: &SkillsConfig, verbose: bool, json: bool) -> anyhow::R
if !verbose {
println!();
println!(
"Use --verbose for details, or 'ironclaw skills info <name>' for a specific skill."
"Use --verbose for details, or 'optimclaw skills info <name>' for a specific skill."
);
}
@@ -251,7 +251,7 @@ async fn cmd_info(config: &SkillsConfig, name: &str, json: bool) -> anyhow::Resu
let registry = discover_skills(config).await;
let skill = registry.find_by_name(name).ok_or_else(|| {
anyhow::anyhow!(
"Skill '{}' not found. Use 'ironclaw skills list' to see available skills.",
"Skill '{}' not found. Use 'optimclaw skills list' to see available skills.",
name
)
})?;
+6 -6
View File
@@ -5,7 +5,7 @@
use std::path::PathBuf;
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::cli::fmt;
use crate::settings::Settings;
@@ -40,7 +40,7 @@ pub async fn run_status_command() -> anyhow::Result<()> {
let settings = load_settings();
println!();
println!(" {}IronClaw Status{}", fmt::bold(), fmt::reset());
println!(" {}OptimClaw Status{}", fmt::bold(), fmt::reset());
println!();
// Version
@@ -91,7 +91,7 @@ pub async fn run_status_command() -> anyhow::Result<()> {
let session_value = if session_path.exists() {
format!("found ({})", session_path.display())
} else {
"not found (run `ironclaw onboard`)".to_string()
"not found (run `optimclaw onboard`)".to_string()
};
println!("{}", fmt::kv_line("Session", &session_value, 12));
@@ -188,7 +188,7 @@ pub async fn run_status_command() -> anyhow::Result<()> {
"{}",
fmt::kv_line(
"Config",
&crate::bootstrap::ironclaw_env_path().display().to_string(),
&crate::bootstrap::optimclaw_env_path().display().to_string(),
12,
)
);
@@ -238,11 +238,11 @@ fn count_wasm_files(dir: &std::path::Path) -> usize {
}
fn default_tools_dir() -> PathBuf {
ironclaw_base_dir().join("tools")
optimclaw_base_dir().join("tools")
}
fn default_channels_dir() -> PathBuf {
ironclaw_base_dir().join("channels")
optimclaw_base_dir().join("channels")
}
#[cfg(test)]
+11 -11
View File
@@ -10,13 +10,13 @@ use std::sync::Arc;
use clap::Subcommand;
use tokio::fs;
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::secrets::{CreateSecretParams, SecretsStore};
use crate::tools::wasm::{CapabilitiesFile, compute_binary_hash};
/// Default tools directory.
fn default_tools_dir() -> PathBuf {
ironclaw_base_dir().join("tools")
optimclaw_base_dir().join("tools")
}
#[derive(Subcommand, Debug, Clone)]
@@ -34,7 +34,7 @@ pub enum ToolCommand {
#[arg(long)]
capabilities: Option<PathBuf>,
/// Target directory for installation (default: ~/.ironclaw/tools/)
/// Target directory for installation (default: ~/.optimclaw/tools/)
#[arg(short, long)]
target: Option<PathBuf>,
@@ -53,7 +53,7 @@ pub enum ToolCommand {
/// List installed tools
List {
/// Directory to list tools from (default: ~/.ironclaw/tools/)
/// Directory to list tools from (default: ~/.optimclaw/tools/)
#[arg(short, long)]
dir: Option<PathBuf>,
@@ -67,7 +67,7 @@ pub enum ToolCommand {
/// Name of the tool to remove
name: String,
/// Directory to remove tool from (default: ~/.ironclaw/tools/)
/// Directory to remove tool from (default: ~/.optimclaw/tools/)
#[arg(short, long)]
dir: Option<PathBuf>,
},
@@ -77,7 +77,7 @@ pub enum ToolCommand {
/// Name of the tool or path to .wasm file
name_or_path: String,
/// Directory to look for tool (default: ~/.ironclaw/tools/)
/// Directory to look for tool (default: ~/.optimclaw/tools/)
#[arg(short, long)]
dir: Option<PathBuf>,
@@ -91,7 +91,7 @@ pub enum ToolCommand {
/// Name of the tool
name: String,
/// Directory to look for tool (default: ~/.ironclaw/tools/)
/// Directory to look for tool (default: ~/.optimclaw/tools/)
#[arg(short, long)]
dir: Option<PathBuf>,
@@ -105,7 +105,7 @@ pub enum ToolCommand {
/// Name of the tool
name: String,
/// Directory to look for tool (default: ~/.ironclaw/tools/)
/// Directory to look for tool (default: ~/.optimclaw/tools/)
#[arg(short, long)]
dir: Option<PathBuf>,
@@ -304,7 +304,7 @@ async fn list_tools(dir: Option<PathBuf>, verbose: bool) -> anyhow::Result<()> {
if !tools_dir.exists() {
println!("No tools directory found at {}", tools_dir.display());
println!("Install a tool with: ironclaw tool install <path>");
println!("Install a tool with: optimclaw tool install <path>");
return Ok(());
}
@@ -1195,7 +1195,7 @@ async fn setup_tool(name: String, dir: Option<PathBuf>, user_id: String) -> anyh
anyhow::anyhow!(
"Tool '{}' has no setup configuration.\n\
The tool may not require setup, or setup is not defined.\n\
Try 'ironclaw tool auth {}' for OAuth-based authentication.",
Try 'optimclaw tool auth {}' for OAuth-based authentication.",
name,
name
)
@@ -1302,7 +1302,7 @@ mod tests {
#[test]
fn test_default_tools_dir() {
let dir = default_tools_dir();
assert!(dir.to_string_lossy().contains(".ironclaw"));
assert!(dir.to_string_lossy().contains(".optimclaw"));
assert!(dir.to_string_lossy().contains("tools"));
}
+4 -4
View File
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use std::path::PathBuf;
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::config::helpers::{optional_env, parse_bool_env, parse_optional_env};
use crate::error::ConfigError;
use crate::settings::Settings;
@@ -14,7 +14,7 @@ pub struct ChannelsConfig {
pub http: Option<HttpConfig>,
pub gateway: Option<GatewayConfig>,
pub signal: Option<SignalConfig>,
/// Directory containing WASM channel modules (default: ~/.ironclaw/channels/).
/// Directory containing WASM channel modules (default: ~/.optimclaw/channels/).
pub wasm_channels_dir: std::path::PathBuf,
/// Whether WASM channels are enabled.
pub wasm_channels_enabled: bool,
@@ -309,9 +309,9 @@ impl ChannelsConfig {
/// other modules that need to construct a gateway URL.
pub const DEFAULT_GATEWAY_PORT: u16 = 3000;
/// Get the default channels directory (~/.ironclaw/channels/).
/// Get the default channels directory (~/.optimclaw/channels/).
fn default_channels_dir() -> PathBuf {
ironclaw_base_dir().join("channels")
optimclaw_base_dir().join("channels")
}
#[cfg(test)]
+6 -6
View File
@@ -2,7 +2,7 @@ use std::path::PathBuf;
use secrecy::{ExposeSecret, SecretString};
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::config::helpers::{optional_env, parse_optional_env};
use crate::error::ConfigError;
@@ -95,7 +95,7 @@ pub struct DatabaseConfig {
pub ssl_mode: SslMode,
// -- libSQL fields --
/// Path to local libSQL database file (default: ~/.ironclaw/ironclaw.db).
/// Path to local libSQL database file (default: ~/.optimclaw/optimclaw.db).
pub libsql_path: Option<PathBuf>,
/// Turso cloud URL for remote sync (optional).
pub libsql_url: Option<String>,
@@ -116,7 +116,7 @@ impl DatabaseConfig {
// PostgreSQL URL is required only when using the postgres backend.
// For libsql backend, default to an empty placeholder.
// DATABASE_URL is loaded from ~/.ironclaw/.env via dotenvy early in startup.
// DATABASE_URL is loaded from ~/.optimclaw/.env via dotenvy early in startup.
let url = optional_env("DATABASE_URL")?
.or_else(|| {
if backend == DatabaseBackend::LibSql {
@@ -127,7 +127,7 @@ impl DatabaseConfig {
})
.ok_or_else(|| ConfigError::MissingRequired {
key: "DATABASE_URL".to_string(),
hint: "Run 'ironclaw onboard' or set DATABASE_URL environment variable".to_string(),
hint: "Run 'optimclaw onboard' or set DATABASE_URL environment variable".to_string(),
})?;
let pool_size = parse_optional_env("DATABASE_POOL_SIZE", 10)?;
@@ -224,9 +224,9 @@ impl SslMode {
}
}
/// Default libSQL database path (~/.ironclaw/ironclaw.db).
/// Default libSQL database path (~/.optimclaw/optimclaw.db).
pub fn default_libsql_path() -> PathBuf {
ironclaw_base_dir().join("ironclaw.db")
optimclaw_base_dir().join("optimclaw.db")
}
#[cfg(test)]
+3 -3
View File
@@ -338,7 +338,7 @@ mod tests {
#[test]
fn runtime_env_override_is_visible_to_env_or_override() {
// Use a unique key that won't collide with real env vars.
let key = "IRONCLAW_TEST_RUNTIME_OVERRIDE_42";
let key = "OPTIMCLAW_TEST_RUNTIME_OVERRIDE_42";
// Not set initially
assert!(env_or_override(key).is_none());
@@ -352,7 +352,7 @@ mod tests {
#[test]
fn runtime_env_override_is_visible_to_optional_env() {
let key = "IRONCLAW_TEST_OPTIONAL_ENV_OVERRIDE_42";
let key = "OPTIMCLAW_TEST_OPTIONAL_ENV_OVERRIDE_42";
assert_eq!(optional_env(key).unwrap(), None);
@@ -364,7 +364,7 @@ mod tests {
#[test]
fn real_env_var_takes_priority_over_runtime_override() {
let _guard = lock_env();
let key = "IRONCLAW_TEST_ENV_PRIORITY_42";
let key = "OPTIMCLAW_TEST_ENV_PRIORITY_42";
// Set runtime override
set_runtime_env(key, "override_value");
+3 -3
View File
@@ -1,4 +1,4 @@
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::config::helpers::{parse_bool_env, parse_optional_env};
use crate::error::ConfigError;
@@ -43,14 +43,14 @@ impl HygieneConfig {
}
/// Convert to the workspace hygiene config, resolving the state directory
/// to the standard `~/.ironclaw` location.
/// to the standard `~/.optimclaw` location.
pub fn to_workspace_config(&self) -> crate::workspace::hygiene::HygieneConfig {
crate::workspace::hygiene::HygieneConfig {
enabled: self.enabled,
daily_retention_days: self.daily_retention_days,
conversation_retention_days: self.conversation_retention_days,
cadence_hours: self.cadence_hours,
state_dir: ironclaw_base_dir(),
state_dir: optimclaw_base_dir(),
}
}
}
+5 -5
View File
@@ -2,7 +2,7 @@ use std::path::PathBuf;
use secrecy::SecretString;
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::config::helpers::{optional_env, parse_optional_env, validate_base_url};
use crate::error::ConfigError;
use crate::llm::config::*;
@@ -18,7 +18,7 @@ impl LlmConfig {
backend: "nearai".to_string(),
session: SessionConfig {
auth_base_url: "http://localhost:0".to_string(),
session_path: std::env::temp_dir().join("ironclaw-test-session.json"),
session_path: std::env::temp_dir().join("optimclaw-test-session.json"),
},
nearai: NearAiConfig {
model: "test-model".to_string(),
@@ -203,7 +203,7 @@ impl LlmConfig {
.unwrap_or_else(|| "app_EMoamEEZ73f0CkXaXp7hrann".to_string());
let session_path = optional_env("OPENAI_CODEX_SESSION_PATH")?
.map(PathBuf::from)
.unwrap_or_else(|| ironclaw_base_dir().join("openai_codex_session.json"));
.unwrap_or_else(|| optimclaw_base_dir().join("openai_codex_session.json"));
let token_refresh_margin_secs =
parse_optional_env("OPENAI_CODEX_REFRESH_MARGIN_SECS", 300)?;
Some(OpenAiCodexConfig {
@@ -527,9 +527,9 @@ fn merge_extra_headers(
merged
}
/// Get the default session file path (~/.ironclaw/session.json).
/// Get the default session file path (~/.optimclaw/session.json).
pub fn default_session_path() -> PathBuf {
ironclaw_base_dir().join("session.json")
optimclaw_base_dir().join("session.json")
}
#[cfg(test)]
+9 -9
View File
@@ -1,7 +1,7 @@
//! Configuration for IronClaw.
//! Configuration for OptimClaw.
//!
//! Settings are loaded with priority: env var > database > default.
//! `DATABASE_URL` lives in `~/.ironclaw/.env` (loaded via dotenvy early
//! `DATABASE_URL` lives in `~/.optimclaw/.env` (loaded via dotenvy early
//! in startup). Everything else comes from env vars, the DB settings
//! table, or auto-detection.
@@ -141,7 +141,7 @@ impl Config {
http: None,
gateway: None,
signal: None,
wasm_channels_dir: std::env::temp_dir().join("ironclaw-test-channels"),
wasm_channels_dir: std::env::temp_dir().join("optimclaw-test-channels"),
wasm_channels_enabled: false,
wasm_channel_owner_ids: HashMap::new(),
},
@@ -202,7 +202,7 @@ impl Config {
toml_path: Option<&std::path::Path>,
) -> Result<Self, ConfigError> {
let _ = dotenvy::dotenv();
crate::bootstrap::load_ironclaw_env();
crate::bootstrap::load_optimclaw_env();
// Load all settings from DB into a Settings struct
let mut db_settings = match store.get_all_settings(user_id).await {
@@ -225,7 +225,7 @@ impl Config {
/// and by CLI commands that don't have DB access.
/// Falls back to legacy `settings.json` on disk if present.
///
/// Loads both `./.env` (standard, higher priority) and `~/.ironclaw/.env`
/// Loads both `./.env` (standard, higher priority) and `~/.optimclaw/.env`
/// (lower priority) via dotenvy, which never overwrites existing vars.
pub async fn from_env() -> Result<Self, ConfigError> {
Self::from_env_with_toml(None).await
@@ -242,7 +242,7 @@ impl Config {
/// Load and merge a TOML config file into settings.
///
/// If `explicit_path` is `Some`, loads from that path (errors are fatal).
/// If `None`, tries the default path `~/.ironclaw/config.toml` (missing
/// If `None`, tries the default path `~/.optimclaw/config.toml` (missing
/// file is silently ignored).
fn apply_toml_overlay(
settings: &mut Settings,
@@ -351,7 +351,7 @@ pub(crate) fn load_bootstrap_settings(
toml_path: Option<&std::path::Path>,
) -> Result<Settings, ConfigError> {
let _ = dotenvy::dotenv();
crate::bootstrap::load_ironclaw_env();
crate::bootstrap::load_optimclaw_env();
let mut settings = Settings::load();
Config::apply_toml_overlay(&mut settings, toml_path)?;
@@ -359,7 +359,7 @@ pub(crate) fn load_bootstrap_settings(
}
pub(crate) fn resolve_owner_id(settings: &Settings) -> Result<String, ConfigError> {
let env_owner_id = self::helpers::optional_env("IRONCLAW_OWNER_ID")?;
let env_owner_id = self::helpers::optional_env("OPTIMCLAW_OWNER_ID")?;
let settings_owner_id = settings.owner_id.clone();
let configured_owner_id = env_owner_id.clone().or(settings_owner_id.clone());
@@ -376,7 +376,7 @@ pub(crate) fn resolve_owner_id(settings: &Settings) -> Result<String, ConfigErro
{
WARNED_EXPLICIT_DEFAULT_OWNER_ID.call_once(|| {
tracing::warn!(
"IRONCLAW_OWNER_ID resolved to the legacy 'default' scope explicitly; durable state will keep legacy owner behavior"
"OPTIMCLAW_OWNER_ID resolved to the legacy 'default' scope explicitly; durable state will keep legacy owner behavior"
);
});
}
+4 -4
View File
@@ -62,8 +62,8 @@ impl RelayConfig {
Some(Self {
url,
api_key,
callback_url: env("IRONCLAW_OAUTH_CALLBACK_URL"),
instance_id: env("IRONCLAW_INSTANCE_ID"),
callback_url: env("OPTIMCLAW_OAUTH_CALLBACK_URL"),
instance_id: env("OPTIMCLAW_INSTANCE_ID"),
request_timeout_secs: env("RELAY_REQUEST_TIMEOUT_SECS")
.and_then(|v| v.parse().ok())
.unwrap_or(30),
@@ -117,8 +117,8 @@ mod tests {
let config = RelayConfig::from_env_reader(|key| match key {
"CHANNEL_RELAY_URL" => Some("http://relay:3001".into()),
"CHANNEL_RELAY_API_KEY" => Some("secret".into()),
"IRONCLAW_OAUTH_CALLBACK_URL" => Some("https://tunnel.example.com".into()),
"IRONCLAW_INSTANCE_ID" => Some("my-instance".into()),
"OPTIMCLAW_OAUTH_CALLBACK_URL" => Some("https://tunnel.example.com".into()),
"OPTIMCLAW_INSTANCE_ID" => Some("my-instance".into()),
"RELAY_REQUEST_TIMEOUT_SECS" => Some("60".into()),
"RELAY_WEBHOOK_PATH" => Some("/custom/events".into()),
_ => None,
+1 -1
View File
@@ -1,7 +1,7 @@
use crate::config::helpers::{parse_bool_env, parse_optional_env};
use crate::error::ConfigError;
pub use ironclaw_safety::SafetyConfig;
pub use optimclaw_safety::SafetyConfig;
pub(crate) fn resolve_safety_config(
settings: &crate::settings::Settings,
+2 -2
View File
@@ -42,7 +42,7 @@ impl Default for SandboxModeConfig {
timeout_secs: 120,
memory_limit_mb: 2048,
cpu_shares: 1024,
image: "ironclaw-worker:latest".to_string(),
image: "optimclaw-worker:latest".to_string(),
auto_pull_image: true,
extra_allowed_domains: Vec::new(),
reaper_interval_secs: 300,
@@ -344,7 +344,7 @@ mod tests {
assert_eq!(cfg.timeout_secs, 120);
assert_eq!(cfg.memory_limit_mb, 2048);
assert_eq!(cfg.cpu_shares, 1024);
assert_eq!(cfg.image, "ironclaw-worker:latest");
assert_eq!(cfg.image, "optimclaw-worker:latest");
assert!(cfg.auto_pull_image);
assert!(cfg.extra_allowed_domains.is_empty());
}
+7 -7
View File
@@ -1,6 +1,6 @@
use std::path::PathBuf;
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::config::helpers::{optional_env, parse_bool_env, parse_optional_env};
use crate::error::ConfigError;
@@ -9,10 +9,10 @@ use crate::error::ConfigError;
pub struct SkillsConfig {
/// Whether the skills system is enabled.
pub enabled: bool,
/// Directory containing user-placed skills (default: ~/.ironclaw/skills/).
/// Directory containing user-placed skills (default: ~/.optimclaw/skills/).
/// Skills here are loaded with `Trusted` trust level.
pub local_dir: PathBuf,
/// Directory containing registry-installed skills (default: ~/.ironclaw/installed_skills/).
/// Directory containing registry-installed skills (default: ~/.optimclaw/installed_skills/).
/// Skills here are loaded with `Installed` trust level and get read-only tool access.
pub installed_dir: PathBuf,
/// Maximum number of skills that can be active simultaneously.
@@ -33,14 +33,14 @@ impl Default for SkillsConfig {
}
}
/// Get the default user skills directory (~/.ironclaw/skills/).
/// Get the default user skills directory (~/.optimclaw/skills/).
fn default_skills_dir() -> PathBuf {
ironclaw_base_dir().join("skills")
optimclaw_base_dir().join("skills")
}
/// Get the default installed skills directory (~/.ironclaw/installed_skills/).
/// Get the default installed skills directory (~/.optimclaw/installed_skills/).
fn default_installed_skills_dir() -> PathBuf {
ironclaw_base_dir().join("installed_skills")
optimclaw_base_dir().join("installed_skills")
}
impl SkillsConfig {
+4 -4
View File
@@ -1,7 +1,7 @@
use std::path::PathBuf;
use std::time::Duration;
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::config::helpers::{optional_env, parse_bool_env, parse_optional_env};
use crate::error::ConfigError;
@@ -10,7 +10,7 @@ use crate::error::ConfigError;
pub struct WasmConfig {
/// Whether WASM tool execution is enabled.
pub enabled: bool,
/// Directory containing installed WASM tools (default: ~/.ironclaw/tools/).
/// Directory containing installed WASM tools (default: ~/.optimclaw/tools/).
pub tools_dir: PathBuf,
/// Default memory limit in bytes (default: 10 MB).
pub default_memory_limit: u64,
@@ -38,9 +38,9 @@ impl Default for WasmConfig {
}
}
/// Get the default tools directory (~/.ironclaw/tools/).
/// Get the default tools directory (~/.optimclaw/tools/).
fn default_tools_dir() -> PathBuf {
ironclaw_base_dir().join("tools")
optimclaw_base_dir().join("tools")
}
impl WasmConfig {
+1 -1
View File
@@ -141,7 +141,7 @@ The `Database` supertrait is composed of seven sub-traits. Leaf consumers can de
```bash
# Use local SQLite file (default)
DATABASE_BACKEND=libsql LIBSQL_PATH=~/.ironclaw/test.db cargo run
DATABASE_BACKEND=libsql LIBSQL_PATH=~/.optimclaw/test.db cargo run
# Use Turso cloud (embedded replica syncs local file to cloud)
DATABASE_BACKEND=libsql LIBSQL_URL=libsql://xxx.turso.io LIBSQL_AUTH_TOKEN=xxx cargo run
+2 -2
View File
@@ -275,7 +275,7 @@ async fn validate_postgres(pool: &deadpool_postgres::Pool) -> Result<(), Databas
if major_version < MIN_PG_MAJOR_VERSION {
return Err(DatabaseError::Pool(format!(
"PostgreSQL {} detected. IronClaw requires PostgreSQL {} or later \
"PostgreSQL {} detected. OptimClaw requires PostgreSQL {} or later \
for pgvector support.\n\
Upgrade: https://www.postgresql.org/download/",
version_str, MIN_PG_MAJOR_VERSION
@@ -301,7 +301,7 @@ async fn validate_postgres(pool: &deadpool_postgres::Pool) -> Result<(), Databas
Ubuntu: apt install postgresql-{0}-pgvector\n \
Docker: use the pgvector/pgvector:pg{0} image\n \
Source: https://github.com/pgvector/pgvector#installation\n\n\
Then restart PostgreSQL and re-run: ironclaw onboard",
Then restart PostgreSQL and re-run: optimclaw onboard",
major_version
)));
}
+2 -2
View File
@@ -1,4 +1,4 @@
//! Error types for IronClaw.
//! Error types for OptimClaw.
use std::time::Duration;
@@ -354,7 +354,7 @@ pub enum WorkerError {
#[error("Worker execution failed: {reason}")]
ExecutionFailed { reason: String },
#[error("Missing worker token (IRONCLAW_WORKER_TOKEN not set)")]
#[error("Missing worker token (OPTIMCLAW_WORKER_TOKEN not set)")]
MissingToken,
}
+1 -1
View File
@@ -22,7 +22,7 @@ impl OnlineDiscovery {
pub fn new() -> Self {
let http_client = reqwest::Client::builder()
.timeout(Duration::from_secs(10))
.user_agent("IronClaw/1.0")
.user_agent("OptimClaw/1.0")
.build()
.unwrap_or_else(|_| reqwest::Client::new());
+37 -37
View File
@@ -292,7 +292,7 @@ fn channel_auth_instructions(
) -> String {
if channel_name == TELEGRAM_CHANNEL_NAME && secret.name == "telegram_bot_token" {
return format!(
"{} After you submit it, IronClaw will show a one-time verification code. Send `/start CODE` to your bot in Telegram and IronClaw will finish setup automatically.",
"{} After you submit it, OptimClaw will show a one-time verification code. Send `/start CODE` to your bot in Telegram and OptimClaw will finish setup automatically.",
secret.prompt
);
}
@@ -326,11 +326,11 @@ fn telegram_verification_deep_link(bot_username: Option<&str>, code: &str) -> Op
fn telegram_verification_instructions(bot_username: Option<&str>, code: &str) -> String {
if let Some(username) = bot_username.filter(|username| !username.trim().is_empty()) {
return format!(
"Send `/start {code}` to @{username} in Telegram. IronClaw will finish setup automatically."
"Send `/start {code}` to @{username} in Telegram. OptimClaw will finish setup automatically."
);
}
format!("Send `/start {code}` to your Telegram bot. IronClaw will finish setup automatically.")
format!("Send `/start {code}` to your Telegram bot. OptimClaw will finish setup automatically.")
}
fn telegram_message_matches_verification_code(text: &str, code: &str) -> bool {
@@ -436,7 +436,7 @@ pub struct ExtensionManager {
/// `/oauth/callback` handler.
pending_oauth_flows: crate::cli::oauth_defaults::PendingOAuthRegistry,
/// OAuth proxy auth token for authenticating with the hosted token exchange proxy.
/// Resolved once at construction from `IRONCLAW_OAUTH_PROXY_AUTH_TOKEN`,
/// Resolved once at construction from `OPTIMCLAW_OAUTH_PROXY_AUTH_TOKEN`,
/// then `GATEWAY_AUTH_TOKEN` as a backward-compatible fallback.
oauth_proxy_auth_token: Option<String>,
/// Relay config captured at startup. Used by `auth_channel_relay` and
@@ -619,7 +619,7 @@ impl ExtensionManager {
/// instead of calling `open::that()` on the server.
///
/// `base_url` is the gateway's own public URL (e.g. `https://my-gateway.example.com`),
/// used to build OAuth redirect URIs when `IRONCLAW_OAUTH_CALLBACK_URL` is not set.
/// used to build OAuth redirect URIs when `OPTIMCLAW_OAUTH_CALLBACK_URL` is not set.
pub async fn enable_gateway_mode(&self, base_url: String) {
self.gateway_mode
.store(true, std::sync::atomic::Ordering::Release);
@@ -631,7 +631,7 @@ impl ExtensionManager {
///
/// Gateway mode is active when any of:
/// - `enable_gateway_mode()` was called (web gateway is running), OR
/// - `IRONCLAW_OAUTH_CALLBACK_URL` is set to a non-loopback URL, OR
/// - `OPTIMCLAW_OAUTH_CALLBACK_URL` is set to a non-loopback URL, OR
/// - `self.tunnel_url` is set to a non-loopback URL
pub fn should_use_gateway_mode(&self) -> bool {
if self.gateway_mode.load(std::sync::atomic::Ordering::Acquire) {
@@ -652,7 +652,7 @@ impl ExtensionManager {
/// Returns the OAuth redirect URI for gateway mode, or `None` for local mode.
///
/// Priority:
/// 1. `IRONCLAW_OAUTH_CALLBACK_URL` env var (via `callback_url()`)
/// 1. `OPTIMCLAW_OAUTH_CALLBACK_URL` env var (via `callback_url()`)
/// 2. `gateway_base_url` (set by `enable_gateway_mode()`)
/// 3. `tunnel_url` (from config)
/// 4. `None` (local/CLI mode)
@@ -1251,7 +1251,7 @@ impl ExtensionManager {
/// Broadcast an extension status change to the web UI via SSE.
async fn broadcast_extension_status(&self, name: &str, status: &str, message: Option<&str>) {
if let Some(ref sse) = *self.sse_manager.read().await {
sse.broadcast(ironclaw_common::AppEvent::ExtensionStatus {
sse.broadcast(optimclaw_common::AppEvent::ExtensionStatus {
extension_name: name.to_string(),
status: status.to_string(),
message: message.map(|m| m.to_string()),
@@ -1756,7 +1756,7 @@ impl ExtensionManager {
.await;
Ok(format!(
"Removed channel '{}'. Restart IronClaw for the change to take effect.",
"Removed channel '{}'. Restart OptimClaw for the change to take effect.",
name
))
}
@@ -2630,7 +2630,7 @@ impl ExtensionManager {
ExtensionError::InstallFailed(format!(
"'{}' requires building from source. Build artifact not found. \
Run `cargo component build --release` in {} first, \
or use `ironclaw registry install {}`.",
or use `optimclaw registry install {}`.",
name,
resolved_dir.display(),
name,
@@ -3720,7 +3720,7 @@ impl ExtensionManager {
}
if let Some(ref sse) = sse_manager {
sse.broadcast(ironclaw_common::AppEvent::AuthCompleted {
sse.broadcast(optimclaw_common::AppEvent::AuthCompleted {
extension_name: ext_name,
success,
message,
@@ -4661,7 +4661,7 @@ impl ExtensionManager {
ExtensionError::Config(e.to_string())
})?;
// Generate CSRF nonce — IronClaw validates this on the callback to ensure
// Generate CSRF nonce — OptimClaw validates this on the callback to ensure
// the OAuth completion is legitimate. Channel-relay embeds it in the signed
// state and appends it to the post-OAuth redirect URL.
let state_nonce = uuid::Uuid::new_v4().to_string();
@@ -7361,7 +7361,7 @@ mod tests {
Ok(TelegramBindingResult::Pending(VerificationChallenge {
code: "iclaw-7qk2m9".to_string(),
instructions:
"Send `/start iclaw-7qk2m9` to @test_hot_bot in Telegram. IronClaw will finish setup automatically."
"Send `/start iclaw-7qk2m9` to @test_hot_bot in Telegram. OptimClaw will finish setup automatically."
.to_string(),
deep_link: Some("https://t.me/test_hot_bot?start=iclaw-7qk2m9".to_string()),
}))
@@ -8363,7 +8363,7 @@ mod tests {
// Regression tests for a bug where MCP OAuth called `open::that()` on the
// server machine instead of returning an auth URL to the gateway frontend.
// The root cause was that `should_use_gateway_mode()` only checked the
// `IRONCLAW_OAUTH_CALLBACK_URL` env var, ignoring `self.tunnel_url`.
// `OPTIMCLAW_OAUTH_CALLBACK_URL` env var, ignoring `self.tunnel_url`.
/// Build a minimal ExtensionManager with a custom tunnel_url.
fn make_manager_with_tunnel(tunnel_url: Option<String>) -> ExtensionManager {
@@ -8377,7 +8377,7 @@ mod tests {
Arc::new(InMemorySecretsStore::new(crypto));
let tools = Arc::new(crate::tools::ToolRegistry::new());
let mcp = Arc::new(McpSessionManager::new());
let dir = std::env::temp_dir().join("ironclaw-test-gateway-mode");
let dir = std::env::temp_dir().join("optimclaw-test-gateway-mode");
ExtensionManager::new(
mcp,
@@ -8398,10 +8398,10 @@ mod tests {
#[test]
fn should_use_gateway_mode_true_for_tunnel_url() {
let _guard = crate::config::helpers::lock_env();
let original = std::env::var("IRONCLAW_OAUTH_CALLBACK_URL").ok();
let original = std::env::var("OPTIMCLAW_OAUTH_CALLBACK_URL").ok();
// SAFETY: Under ENV_MUTEX, no concurrent env access.
unsafe {
std::env::remove_var("IRONCLAW_OAUTH_CALLBACK_URL");
std::env::remove_var("OPTIMCLAW_OAUTH_CALLBACK_URL");
}
let mgr = make_manager_with_tunnel(Some("https://my-gateway.example.com".into()));
@@ -8412,7 +8412,7 @@ mod tests {
unsafe {
if let Some(val) = original {
std::env::set_var("IRONCLAW_OAUTH_CALLBACK_URL", val);
std::env::set_var("OPTIMCLAW_OAUTH_CALLBACK_URL", val);
}
}
}
@@ -8420,9 +8420,9 @@ mod tests {
#[test]
fn should_use_gateway_mode_false_without_tunnel() {
let _guard = crate::config::helpers::lock_env();
let original = std::env::var("IRONCLAW_OAUTH_CALLBACK_URL").ok();
let original = std::env::var("OPTIMCLAW_OAUTH_CALLBACK_URL").ok();
unsafe {
std::env::remove_var("IRONCLAW_OAUTH_CALLBACK_URL");
std::env::remove_var("OPTIMCLAW_OAUTH_CALLBACK_URL");
}
let mgr = make_manager_with_tunnel(None);
@@ -8433,7 +8433,7 @@ mod tests {
unsafe {
if let Some(val) = original {
std::env::set_var("IRONCLAW_OAUTH_CALLBACK_URL", val);
std::env::set_var("OPTIMCLAW_OAUTH_CALLBACK_URL", val);
}
}
}
@@ -8441,9 +8441,9 @@ mod tests {
#[test]
fn should_use_gateway_mode_false_for_loopback_tunnel() {
let _guard = crate::config::helpers::lock_env();
let original = std::env::var("IRONCLAW_OAUTH_CALLBACK_URL").ok();
let original = std::env::var("OPTIMCLAW_OAUTH_CALLBACK_URL").ok();
unsafe {
std::env::remove_var("IRONCLAW_OAUTH_CALLBACK_URL");
std::env::remove_var("OPTIMCLAW_OAUTH_CALLBACK_URL");
}
let mgr = make_manager_with_tunnel(Some("http://127.0.0.1:3001".into()));
@@ -8454,13 +8454,13 @@ mod tests {
unsafe {
if let Some(val) = original {
std::env::set_var("IRONCLAW_OAUTH_CALLBACK_URL", val);
std::env::set_var("OPTIMCLAW_OAUTH_CALLBACK_URL", val);
}
}
}
/// Helper to run an async test body while holding the env mutex.
/// Clears `IRONCLAW_OAUTH_CALLBACK_URL` for the duration, restoring on drop.
/// Clears `OPTIMCLAW_OAUTH_CALLBACK_URL` for the duration, restoring on drop.
struct EnvGuard {
original: Option<String>,
_mutex: std::sync::MutexGuard<'static, ()>,
@@ -8469,10 +8469,10 @@ mod tests {
impl EnvGuard {
fn new() -> Self {
let guard = crate::config::helpers::lock_env();
let original = std::env::var("IRONCLAW_OAUTH_CALLBACK_URL").ok();
let original = std::env::var("OPTIMCLAW_OAUTH_CALLBACK_URL").ok();
// SAFETY: Under ENV_MUTEX, no concurrent env access.
unsafe {
std::env::remove_var("IRONCLAW_OAUTH_CALLBACK_URL");
std::env::remove_var("OPTIMCLAW_OAUTH_CALLBACK_URL");
}
Self {
original,
@@ -8486,9 +8486,9 @@ mod tests {
// SAFETY: Under ENV_MUTEX (still held by _mutex), no concurrent env access.
unsafe {
if let Some(ref val) = self.original {
std::env::set_var("IRONCLAW_OAUTH_CALLBACK_URL", val);
std::env::set_var("OPTIMCLAW_OAUTH_CALLBACK_URL", val);
} else {
std::env::remove_var("IRONCLAW_OAUTH_CALLBACK_URL");
std::env::remove_var("OPTIMCLAW_OAUTH_CALLBACK_URL");
}
}
}
@@ -8527,10 +8527,10 @@ mod tests {
#[test]
fn gateway_callback_redirect_uri_does_not_duplicate_callback_path_from_env() {
let _guard = crate::config::helpers::lock_env();
let original = std::env::var("IRONCLAW_OAUTH_CALLBACK_URL").ok();
let original = std::env::var("OPTIMCLAW_OAUTH_CALLBACK_URL").ok();
unsafe {
std::env::set_var(
"IRONCLAW_OAUTH_CALLBACK_URL",
"OPTIMCLAW_OAUTH_CALLBACK_URL",
"https://oauth.test.example/oauth/callback",
);
}
@@ -8543,9 +8543,9 @@ mod tests {
unsafe {
if let Some(val) = original {
std::env::set_var("IRONCLAW_OAUTH_CALLBACK_URL", val);
std::env::set_var("OPTIMCLAW_OAUTH_CALLBACK_URL", val);
} else {
std::env::remove_var("IRONCLAW_OAUTH_CALLBACK_URL");
std::env::remove_var("OPTIMCLAW_OAUTH_CALLBACK_URL");
}
}
}
@@ -8553,10 +8553,10 @@ mod tests {
#[test]
fn gateway_callback_redirect_uri_trims_trailing_slash_from_env_callback() {
let _guard = crate::config::helpers::lock_env();
let original = std::env::var("IRONCLAW_OAUTH_CALLBACK_URL").ok();
let original = std::env::var("OPTIMCLAW_OAUTH_CALLBACK_URL").ok();
unsafe {
std::env::set_var(
"IRONCLAW_OAUTH_CALLBACK_URL",
"OPTIMCLAW_OAUTH_CALLBACK_URL",
"https://oauth.test.example/oauth/callback/",
);
}
@@ -8569,9 +8569,9 @@ mod tests {
unsafe {
if let Some(val) = original {
std::env::set_var("IRONCLAW_OAUTH_CALLBACK_URL", val);
std::env::set_var("OPTIMCLAW_OAUTH_CALLBACK_URL", val);
} else {
std::env::remove_var("IRONCLAW_OAUTH_CALLBACK_URL");
std::env::remove_var("OPTIMCLAW_OAUTH_CALLBACK_URL");
}
}
}
+1 -1
View File
@@ -223,7 +223,7 @@ fn score_entry(entry: &RegistryEntry, tokens: &[String]) -> u32 {
score
}
/// Well-known extensions that ship with ironclaw.
/// Well-known extensions that ship with optimclaw.
///
/// If `relay_url` is provided, a channel-relay Slack entry is included in the list.
/// Pass `None` when the relay is not configured.
+1 -1
View File
@@ -132,7 +132,7 @@ impl HookRegistrationSummary {
}
}
/// Register bundled built-in hooks that ship with IronClaw.
/// Register bundled built-in hooks that ship with OptimClaw.
pub async fn register_bundled_hooks(registry: &Arc<HookRegistry>) -> HookRegistrationSummary {
registry
.register_with_priority(Arc::new(AuditLogHook), 25)
+1 -1
View File
@@ -1,7 +1,7 @@
//! OpenClaw migration and import functionality.
//!
//! Provides tools to migrate existing OpenClaw installations (memory, history,
//! settings, and credentials) into IronClaw without data loss.
//! settings, and credentials) into OptimClaw without data loss.
#[cfg(feature = "import")]
pub mod openclaw;
+1 -1
View File
@@ -7,7 +7,7 @@ use crate::import::{ImportError, ImportOptions};
use super::reader::OpenClawMemoryChunk;
/// Import a single memory chunk into IronClaw.
/// Import a single memory chunk into OptimClaw.
pub async fn import_chunk(
db: &Arc<dyn Database>,
chunk: &OpenClawMemoryChunk,
+2 -2
View File
@@ -1,11 +1,11 @@
//! OpenClaw configuration to IronClaw settings mapping.
//! OpenClaw configuration to OptimClaw settings mapping.
use secrecy::SecretString;
use std::collections::HashMap;
use super::reader::OpenClawConfig;
/// Map OpenClaw configuration to IronClaw settings (dotted-key format).
/// Map OpenClaw configuration to OptimClaw settings (dotted-key format).
pub fn map_openclaw_config_to_settings(
config: &OpenClawConfig,
) -> HashMap<String, serde_json::Value> {
+7 -7
View File
@@ -25,7 +25,7 @@ Multi-provider LLM integration with circuit breaker, retry, failover, and respon
| `costs.rs` | Static per-model cost table (OpenAI, Anthropic, local/Ollama heuristics) |
| `rig_adapter.rs` | Adapter bridging rig-core `CompletionModel``LlmProvider`; used by OpenAI, Anthropic, Ollama, Tinfoil |
| `smart_routing.rs` | `SmartRoutingProvider` — 13-dimension complexity scorer routes cheap vs primary model |
| `recording.rs` | `RecordingLlm` — trace capture for E2E replay testing (`IRONCLAW_RECORD_TRACE`) |
| `recording.rs` | `RecordingLlm` — trace capture for E2E replay testing (`OPTIMCLAW_RECORD_TRACE`) |
| `bedrock.rs` | AWS Bedrock provider via native Converse API (feature-gated: `--features bedrock`) |
## Provider Selection
@@ -46,8 +46,8 @@ Set via `LLM_BACKEND` env var:
Codex auth reuse:
- Set `LLM_USE_CODEX_AUTH=true` to load credentials from `~/.codex/auth.json` (override with `CODEX_AUTH_PATH`).
- If Codex is logged in with API-key mode, IronClaw uses the standard OpenAI endpoint.
- If Codex is logged in with ChatGPT OAuth mode, IronClaw routes to the private `chatgpt.com/backend-api/codex` Responses API via `codex_chatgpt.rs`.
- If Codex is logged in with API-key mode, OptimClaw uses the standard OpenAI endpoint.
- If Codex is logged in with ChatGPT OAuth mode, OptimClaw routes to the private `chatgpt.com/backend-api/codex` Responses API via `codex_chatgpt.rs`.
- ChatGPT mode supports one automatic 401 refresh using the refresh token persisted in `auth.json`.
## AWS Bedrock Provider
@@ -85,7 +85,7 @@ ID, migrate to it immediately. Advanced users can override headers via
## NEAR AI Provider Gotchas
**Dual auth modes:**
- **Session token** (default): `NEARAI_SESSION_TOKEN=sess_...`, base URL = `https://private.near.ai`. Tokens are persisted to `~/.ironclaw/session.json` (mode 0600) and optionally to the DB `settings` table (`nearai.session_token`). On 401 responses where the body contains "session" + "expired"/"invalid", `NearAiChatProvider` calls `session.handle_auth_failure()` which triggers the interactive OAuth login flow and retries once. Plain `AuthFailed` 401s are not retried.
- **Session token** (default): `NEARAI_SESSION_TOKEN=sess_...`, base URL = `https://private.near.ai`. Tokens are persisted to `~/.optimclaw/session.json` (mode 0600) and optionally to the DB `settings` table (`nearai.session_token`). On 401 responses where the body contains "session" + "expired"/"invalid", `NearAiChatProvider` calls `session.handle_auth_failure()` which triggers the interactive OAuth login flow and retries once. Plain `AuthFailed` 401s are not retried.
- **API key**: Set `NEARAI_API_KEY` (from `cloud.near.ai`), base URL defaults to `https://cloud-api.near.ai`. 401s with API key auth are immediately returned as `LlmError::AuthFailed` — no renewal.
**Session renewal is interactive:** When `SessionExpired` triggers renewal, it blocks and prompts the user in the terminal (GitHub/Google OAuth or manual API key entry). This is unsuitable for headless/hosted deployments — set `NEARAI_SESSION_TOKEN` env var instead.
@@ -178,7 +178,7 @@ Set `LLM_EXTRA_HEADERS=Key:Value,Key2:Value2` to inject headers into every reque
Uses the Responses API at `chatgpt.com/backend-api/codex/responses` with ChatGPT subscription OAuth tokens (zero API cost — billing through subscription).
**Auth flow:** Device code OAuth via `auth.openai.com/api/accounts/deviceauth/*` endpoints. On first run, displays a code for the user to enter at a URL. Tokens are persisted to `~/.ironclaw/openai_codex_session.json` (mode 0600) and auto-refreshed before expiry.
**Auth flow:** Device code OAuth via `auth.openai.com/api/accounts/deviceauth/*` endpoints. On first run, displays a code for the user to enter at a URL. Tokens are persisted to `~/.optimclaw/openai_codex_session.json` (mode 0600) and auto-refreshed before expiry.
**Provider chain:** `OpenAiCodexProvider``TokenRefreshingProvider` (pre-emptive refresh + retry on 401) → standard decorator chain. The `TokenRefreshingProvider` intercepts `AuthFailed`/`SessionExpired` errors, refreshes the OAuth token, and retries once.
@@ -203,7 +203,7 @@ Raw provider
→ FailoverProvider (fallback model; only when NEARAI_FALLBACK_MODEL is set)
→ CircuitBreakerProvider (fast-fail; only when NEARAI_CIRCUIT_BREAKER_THRESHOLD is set)
→ CachedProvider (response cache; only when NEARAI_RESPONSE_CACHE_ENABLED=true)
→ RecordingLlm (trace capture; only when IRONCLAW_RECORD_TRACE is set)
→ RecordingLlm (trace capture; only when OPTIMCLAW_RECORD_TRACE is set)
```
`build_provider_chain()` also returns a separate standalone cheap LLM provider (for heartbeat/evaluation tasks — not part of the decorator chain).
@@ -238,4 +238,4 @@ No streaming support. All providers use non-streaming (blocking) Chat Completion
## Trace Recording
Set `IRONCLAW_RECORD_TRACE=1` to enable live trace recording via `RecordingLlm`. Traces are JSON files containing: memory snapshot, HTTP exchanges from tools, and LLM steps (user inputs, text responses, tool call responses). Replay these in E2E tests via `TraceLlm`. Configure output path with `IRONCLAW_TRACE_OUTPUT` (default: `trace_{timestamp}.json`).
Set `OPTIMCLAW_RECORD_TRACE=1` to enable live trace recording via `RecordingLlm`. Traces are JSON files containing: memory snapshot, HTTP exchanges from tools, and LLM steps (user inputs, text responses, tool call responses). Replay these in E2E tests via `TraceLlm`. Configure output path with `OPTIMCLAW_TRACE_OUTPUT` (default: `trace_{timestamp}.json`).
+3 -3
View File
@@ -272,7 +272,7 @@ fn build_inference_config(
// Message conversion
// ---------------------------------------------------------------------------
/// Convert IronClaw `ChatMessage` list into Bedrock system blocks + messages.
/// Convert OptimClaw `ChatMessage` list into Bedrock system blocks + messages.
///
/// Key differences from OpenAI/Anthropic protocol:
/// 1. System messages are extracted and passed separately.
@@ -442,7 +442,7 @@ fn push_message(
// Tool configuration
// ---------------------------------------------------------------------------
/// Build Bedrock `ToolConfiguration` from IronClaw tool definitions.
/// Build Bedrock `ToolConfiguration` from OptimClaw tool definitions.
fn build_tool_config(
tools: &[ToolDefinition],
tool_choice: Option<&str>,
@@ -544,7 +544,7 @@ fn extract_token_usage(usage: Option<&aws_sdk_bedrockruntime::types::TokenUsage>
}
}
/// Map Bedrock `StopReason` to IronClaw `FinishReason`.
/// Map Bedrock `StopReason` to OptimClaw `FinishReason`.
fn map_stop_reason(reason: &StopReason) -> FinishReason {
match reason {
StopReason::EndTurn | StopReason::StopSequence => FinishReason::Stop,
+2 -2
View File
@@ -1,8 +1,8 @@
//! Read Codex CLI credentials for LLM authentication.
//!
//! When `LLM_USE_CODEX_AUTH=true`, IronClaw reads the Codex CLI's
//! When `LLM_USE_CODEX_AUTH=true`, OptimClaw reads the Codex CLI's
//! `auth.json` file (default: `~/.codex/auth.json`) and extracts
//! credentials. This lets IronClaw piggyback on a Codex login without
//! credentials. This lets OptimClaw piggyback on a Codex login without
//! implementing its own OAuth flow.
//!
//! Codex supports two auth modes:
+3 -3
View File
@@ -199,7 +199,7 @@ impl CodexChatGptProvider {
.unwrap_or_default()
}
/// Convert IronClaw messages to Responses API request JSON.
/// Convert OptimClaw messages to Responses API request JSON.
fn build_request_body(
&self,
model: &str,
@@ -640,7 +640,7 @@ impl CodexChatGptProvider {
/// Remove keys with empty-string values from a JSON object.
///
/// gpt-5.2-codex fills optional tool parameters with `""` (e.g.
/// `"timestamp": ""`). IronClaw's tool validation treats these as
/// `"timestamp": ""`). OptimClaw's tool validation treats these as
/// invalid "non-empty input expected". Stripping them makes the
/// tool see only the actually-provided values.
fn strip_empty_string_values(value: Value) -> Value {
@@ -725,7 +725,7 @@ impl LlmProvider for CodexChatGptProvider {
let args: Value =
serde_json::from_str(&tc.arguments).unwrap_or_else(|_| json!(tc.arguments));
// gpt-5.2-codex fills optional parameters with empty strings (e.g.
// `"timestamp": ""`), which IronClaw's tool validation rejects.
// `"timestamp": ""`), which OptimClaw's tool validation rejects.
// Strip them so only actually-provided values reach the tool.
let args = Self::strip_empty_string_values(args);
ToolCall {
+3 -3
View File
@@ -9,7 +9,7 @@ use std::path::PathBuf;
use secrecy::SecretString;
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::llm::registry::ProviderProtocol;
use crate::llm::session::SessionConfig;
@@ -114,7 +114,7 @@ pub struct OpenAiCodexConfig {
pub api_base_url: String,
/// OAuth client ID (default: OpenAI's public Codex client).
pub client_id: String,
/// Path to session file (default: ~/.ironclaw/openai_codex_session.json).
/// Path to session file (default: ~/.optimclaw/openai_codex_session.json).
pub session_path: PathBuf,
/// Seconds before expiry to proactively refresh (default: 300).
pub token_refresh_margin_secs: u64,
@@ -127,7 +127,7 @@ impl Default for OpenAiCodexConfig {
auth_endpoint: "https://auth.openai.com".to_string(),
api_base_url: "https://chatgpt.com/backend-api/codex".to_string(),
client_id: "app_EMoamEEZ73f0CkXaXp7hrann".to_string(),
session_path: ironclaw_base_dir().join("openai_codex_session.json"),
session_path: optimclaw_base_dir().join("openai_codex_session.json"),
token_refresh_margin_secs: 300,
}
}
+3 -3
View File
@@ -58,7 +58,7 @@ fn oauth_client_secret() -> String {
}
const OAUTH_SCOPE: &str = "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile";
const GOOG_API_CLIENT: &str = concat!("gl-rust/1.0.0 ironclaw/", env!("CARGO_PKG_VERSION"));
const GOOG_API_CLIENT: &str = concat!("gl-rust/1.0.0 optimclaw/", env!("CARGO_PKG_VERSION"));
const PKCE_CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
const STATE_CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@@ -1205,7 +1205,7 @@ impl GeminiOauthProvider {
headers.insert(
"User-Agent",
format!(
"GeminiCLI-ironclaw/{}/{} ({}; {}; cli)",
"GeminiCLI-optimclaw/{}/{} ({}; {}; cli)",
env!("CARGO_PKG_VERSION"),
self.config.model,
std::env::consts::OS,
@@ -1787,7 +1787,7 @@ impl GeminiOauthProvider {
// Budget cap of 8192 prevents runaway thinking loops.
//
// NOTE: We do NOT set includeThoughts=true. The original Gemini CLI
// sets it because it displays thoughts to the user. IronClaw's reasoning
// sets it because it displays thoughts to the user. OptimClaw's reasoning
// layer (reasoning.rs) strips all <thinking> tags from responses, so
// including thoughts just adds text that gets stripped, potentially
// leaving an empty response.
+1 -1
View File
@@ -499,7 +499,7 @@ struct OpenAiUsage {
completion_tokens: u32,
}
/// Convert IronClaw messages to OpenAI Chat Completions format.
/// Convert OptimClaw messages to OpenAI Chat Completions format.
fn convert_messages(messages: Vec<ChatMessage>) -> Vec<OpenAiMessage> {
messages
.into_iter()
+1 -1
View File
@@ -12,7 +12,7 @@ use tokio::sync::RwLock;
//
// **Known risks:**
// • GitHub may rotate or revoke this client ID at any time, which would
// break authentication for all IronClaw users until the constant is
// break authentication for all OptimClaw users until the constant is
// updated and a new release is shipped.
// • Using another product's client ID may violate GitHub's Terms of
// Service. Maintainers should seek explicit guidance from GitHub
+1 -1
View File
@@ -297,7 +297,7 @@ fn create_openai_compat_from_registry(
// Use CompletionsClient (Chat Completions API) instead of the default
// Client (Responses API). The Responses API path in rig-core handles
// tool results differently, which breaks IronClaw's tool call flow.
// tool results differently, which breaks OptimClaw's tool call flow.
let client = client.completions_api();
let model = client.completion_model(&config.model);
+4 -4
View File
@@ -35,11 +35,11 @@ pub enum OAuthCallbackError {
/// Returns the OAuth callback base URL.
///
/// Checks `IRONCLAW_OAUTH_CALLBACK_URL` env var first (useful for remote/VPS
/// Checks `OPTIMCLAW_OAUTH_CALLBACK_URL` env var first (useful for remote/VPS
/// deployments where `127.0.0.1` is unreachable from the user's browser),
/// then falls back to `http://{callback_host()}:{OAUTH_CALLBACK_PORT}`.
pub fn callback_url() -> String {
crate::config::helpers::env_or_override("IRONCLAW_OAUTH_CALLBACK_URL")
crate::config::helpers::env_or_override("OPTIMCLAW_OAUTH_CALLBACK_URL")
.unwrap_or_else(|| format!("http://{}:{}", callback_host(), OAUTH_CALLBACK_PORT))
}
@@ -301,7 +301,7 @@ pub fn landing_html(provider_name: &str, success: bool) -> String {
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>IronClaw - {heading}</title>
<title>OptimClaw - {heading}</title>
<style>
* {{ margin:0; padding:0; box-sizing:border-box }}
body {{
@@ -347,7 +347,7 @@ pub fn landing_html(provider_name: &str, success: bool) -> String {
{icon}
<h1>{heading}</h1>
<p>{subtitle}</p>
<div class="brand">IronClaw</div>
<div class="brand">OptimClaw</div>
</div>
</body>
</html>"#,
+2 -2
View File
@@ -106,11 +106,11 @@ impl OpenAiCodexProvider {
);
headers.insert(
HeaderName::from_static("originator"),
HeaderValue::from_static("ironclaw"),
HeaderValue::from_static("optimclaw"),
);
headers.insert(
USER_AGENT,
HeaderValue::from_static(concat!("ironclaw/", env!("CARGO_PKG_VERSION"))),
HeaderValue::from_static(concat!("optimclaw/", env!("CARGO_PKG_VERSION"))),
);
headers.insert(ACCEPT, HeaderValue::from_static("text/event-stream"));
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
+2 -2
View File
@@ -4,7 +4,7 @@
//! - **Device Code** (primary): Works on headless servers, no browser needed.
//! - **Browser PKCE** (fallback): Standard OAuth for local machines.
//!
//! Tokens are persisted to `~/.ironclaw/openai_codex_session.json` and
//! Tokens are persisted to `~/.optimclaw/openai_codex_session.json` and
//! auto-refreshed before expiry.
use chrono::{DateTime, Utc};
@@ -164,7 +164,7 @@ impl OpenAiCodexSessionManager {
let mut headers = HeaderMap::new();
headers.insert(
USER_AGENT,
HeaderValue::from_static(concat!("ironclaw/", env!("CARGO_PKG_VERSION"))),
HeaderValue::from_static(concat!("optimclaw/", env!("CARGO_PKG_VERSION"))),
);
let client = Client::builder()
.default_headers(headers)
+3 -3
View File
@@ -979,7 +979,7 @@ Example:
};
format!(
r#"You are IronClaw Agent, a secure autonomous assistant.
r#"You are OptimClaw Agent, a secure autonomous assistant.
{response_format}
@@ -2168,10 +2168,10 @@ That's my plan."#;
#[test]
fn test_clean_response_thinking_tags_reasoning_properly_tagged() {
let input = "<thinking>The user is asking about my name.</thinking>\n\nI'm IronClaw, a secure personal AI assistant.";
let input = "<thinking>The user is asking about my name.</thinking>\n\nI'm OptimClaw, a secure personal AI assistant.";
assert_eq!(
clean_response(input),
"I'm IronClaw, a secure personal AI assistant."
"I'm OptimClaw, a secure personal AI assistant."
);
}
+2 -2
View File
@@ -2,7 +2,7 @@
//!
//! Models with native thinking support produce structured chain-of-thought
//! via `reasoning_content` fields or built-in `<think>` tags. Injecting
//! IronClaw's own `<think>/<final>` format instructions into the system
//! OptimClaw's own `<think>/<final>` format instructions into the system
//! prompt collides with these models' native behavior, causing:
//! - Thinking-only responses with no visible content
//! - Double-wrapped thinking tags that confuse response cleaning
@@ -55,7 +55,7 @@ const NATIVE_THINKING_PATTERNS: &[&str] = &[
/// Check if a model name indicates native thinking/reasoning support.
///
/// Models that return `true` should NOT have IronClaw's `<think>/<final>`
/// Models that return `true` should NOT have OptimClaw's `<think>/<final>`
/// format instructions injected into their system prompt, as this collides
/// with their built-in reasoning behavior.
///
+8 -8
View File
@@ -10,7 +10,7 @@
//! - **Steps**: user inputs, LLM responses (text/tool_calls), and expected tool
//! results for verifying tool output during replay
//!
//! Enable by setting `IRONCLAW_RECORD_TRACE=1` at runtime.
//! Enable by setting `OPTIMCLAW_RECORD_TRACE=1` at runtime.
use std::collections::VecDeque;
use std::path::PathBuf;
@@ -287,16 +287,16 @@ impl RecordingLlm {
/// Create from environment variables if recording is enabled.
///
/// - `IRONCLAW_RECORD_TRACE` — any non-empty value enables recording
/// - `IRONCLAW_TRACE_OUTPUT` — file path (default: `./trace_{timestamp}.json`)
/// - `IRONCLAW_TRACE_MODEL_NAME` — model_name field (default: `recorded-{inner.model_name()}`)
/// - `OPTIMCLAW_RECORD_TRACE` — any non-empty value enables recording
/// - `OPTIMCLAW_TRACE_OUTPUT` — file path (default: `./trace_{timestamp}.json`)
/// - `OPTIMCLAW_TRACE_MODEL_NAME` — model_name field (default: `recorded-{inner.model_name()}`)
pub fn from_env(inner: Arc<dyn LlmProvider>) -> Option<Arc<Self>> {
let enabled = std::env::var("IRONCLAW_RECORD_TRACE")
let enabled = std::env::var("OPTIMCLAW_RECORD_TRACE")
.ok()
.filter(|v| !v.is_empty());
enabled?;
let output_path = std::env::var("IRONCLAW_TRACE_OUTPUT")
let output_path = std::env::var("OPTIMCLAW_TRACE_OUTPUT")
.ok()
.filter(|v| !v.is_empty())
.map(PathBuf::from)
@@ -305,7 +305,7 @@ impl RecordingLlm {
PathBuf::from(format!("trace_{ts}.json"))
});
let model_name = std::env::var("IRONCLAW_TRACE_MODEL_NAME")
let model_name = std::env::var("OPTIMCLAW_TRACE_MODEL_NAME")
.ok()
.filter(|v| !v.is_empty())
.unwrap_or_else(|| format!("recorded-{}", inner.model_name()));
@@ -773,7 +773,7 @@ mod tests {
#[test]
fn from_env_returns_none_when_unset() {
// SAFETY: This test is single-threaded and no other thread reads this var.
unsafe { std::env::remove_var("IRONCLAW_RECORD_TRACE") };
unsafe { std::env::remove_var("OPTIMCLAW_RECORD_TRACE") };
let stub = Arc::new(StubLlm::new("response"));
let result = RecordingLlm::from_env(stub);
assert!(result.is_none());
+4 -4
View File
@@ -5,7 +5,7 @@
//!
//! ```text
//! ┌─────────────────────┐ ┌──────────────────────────┐
//! │ providers.json │ │ ~/.ironclaw/providers.json│
//! │ providers.json │ │ ~/.optimclaw/providers.json│
//! │ (built-in, embed) │ │ (user overrides/extras) │
//! └────────┬────────────┘ └────────────┬─────────────┘
//! │ │
@@ -192,7 +192,7 @@ pub struct ProviderDefinition {
/// Registry of known LLM providers.
///
/// Built from compiled-in `providers.json` plus optional user overrides
/// from `~/.ironclaw/providers.json`.
/// from `~/.optimclaw/providers.json`.
pub struct ProviderRegistry {
providers: Vec<ProviderDefinition>,
/// Lowercase id/alias → index into `providers`.
@@ -216,7 +216,7 @@ impl ProviderRegistry {
/// Load the default registry: built-in providers + user overrides.
///
/// User providers from `~/.ironclaw/providers.json` are appended,
/// User providers from `~/.optimclaw/providers.json` are appended,
/// with later entries overriding earlier ones by ID/alias.
pub fn load() -> Self {
let builtins: Vec<ProviderDefinition> =
@@ -320,7 +320,7 @@ impl ProviderRegistry {
}
fn user_providers_path() -> Option<std::path::PathBuf> {
Some(crate::bootstrap::ironclaw_base_dir().join("providers.json"))
Some(crate::bootstrap::optimclaw_base_dir().join("providers.json"))
}
#[cfg(test)]
+5 -5
View File
@@ -1,4 +1,4 @@
//! Generic adapter that bridges rig-core's `CompletionModel` trait to IronClaw's `LlmProvider`.
//! Generic adapter that bridges rig-core's `CompletionModel` trait to OptimClaw's `LlmProvider`.
//!
//! This lets us use any rig-core provider (OpenAI, Anthropic, Ollama, etc.) as an
//! `Arc<dyn LlmProvider>` without changing any of the agent, reasoning, or tool code.
@@ -279,7 +279,7 @@ fn make_nullable(schema: &mut JsonValue) {
}
}
/// Convert IronClaw messages to rig-core format.
/// Convert OptimClaw messages to rig-core format.
///
/// Returns `(preamble, chat_history)` where preamble is extracted from
/// any System message and chat_history contains the rest.
@@ -445,7 +445,7 @@ fn normalized_tool_call_id(raw: Option<&str>, seed: usize) -> String {
super::provider::generate_tool_call_id(seed, 0)
}
/// Convert IronClaw tool definitions to rig-core format.
/// Convert OptimClaw tool definitions to rig-core format.
///
/// Applies OpenAI strict-mode schema normalization to ensure all tool
/// parameter schemas comply with OpenAI's function calling requirements.
@@ -460,7 +460,7 @@ fn convert_tools(tools: &[IronToolDefinition]) -> Vec<RigToolDefinition> {
.collect()
}
/// Convert IronClaw tool_choice string to rig-core ToolChoice.
/// Convert OptimClaw tool_choice string to rig-core ToolChoice.
fn convert_tool_choice(choice: Option<&str>) -> Option<RigToolChoice> {
match choice.map(|s| s.to_lowercase()).as_deref() {
Some("auto") => Some(RigToolChoice::Auto),
@@ -493,7 +493,7 @@ fn extract_response(
reasoning: None,
});
}
// Reasoning and Image variants are not mapped to IronClaw types
// Reasoning and Image variants are not mapped to OptimClaw types
_ => {}
}
}
+4 -4
View File
@@ -1,7 +1,7 @@
//! Session management for NEAR AI authentication.
//!
//! Handles session token persistence, expiration detection, and renewal via
//! OAuth flow. Tokens are stored in `~/.ironclaw/session.json` and refreshed
//! OAuth flow. Tokens are stored in `~/.optimclaw/session.json` and refreshed
//! automatically when expired.
use std::path::PathBuf;
@@ -31,7 +31,7 @@ pub struct SessionData {
pub struct SessionConfig {
/// Base URL for auth endpoints (e.g., https://private.near.ai).
pub auth_base_url: String,
/// Path to session file (e.g., ~/.ironclaw/session.json).
/// Path to session file (e.g., ~/.optimclaw/session.json).
pub session_path: PathBuf,
}
@@ -376,7 +376,7 @@ impl SessionManager {
/// cloud.near.ai. The key is stored in the thread-safe runtime
/// env overlay (via `set_runtime_env`) so `LlmConfig::resolve()`
/// auto-selects ChatCompletions mode, and persisted to
/// `~/.ironclaw/.env` for survival across restarts.
/// `~/.optimclaw/.env` for survival across restarts.
/// No session token is saved and no `/v1/users/me` validation is
/// performed (different auth model).
async fn api_key_login(&self) -> Result<(), LlmError> {
@@ -410,7 +410,7 @@ impl SessionManager {
// multi-threaded programs (Rust 1.82+).
crate::config::helpers::set_runtime_env("NEARAI_API_KEY", &key);
// Persist to ~/.ironclaw/.env so the key survives restarts
// Persist to ~/.optimclaw/.env so the key survives restarts
// (bootstrap layer — available before DB is connected).
// Uses upsert to avoid clobbering existing bootstrap vars.
if let Err(e) = crate::bootstrap::upsert_bootstrap_var("NEARAI_API_KEY", &key) {
+1 -1
View File
@@ -224,7 +224,7 @@ pub const DEFAULT_DOMAIN_KEYWORDS: &[&str] = &[
"multisig",
"treasury",
"openclaw",
"ironclaw",
"optimclaw",
];
/// Configuration for the complexity scorer.
+64 -64
View File
@@ -1,11 +1,11 @@
//! IronClaw - Main entry point.
//! OptimClaw - Main entry point.
use std::sync::Arc;
use std::time::Duration;
use clap::Parser;
use ironclaw::{
use optimclaw::{
agent::{Agent, AgentDeps},
app::{AppBuilder, AppBuilderFlags},
channels::{
@@ -28,15 +28,15 @@ use ironclaw::{
};
#[cfg(unix)]
use ironclaw::channels::ChannelSecretUpdater;
use optimclaw::channels::ChannelSecretUpdater;
#[cfg(any(feature = "postgres", feature = "libsql"))]
use ironclaw::setup::{SetupConfig, SetupWizard};
use optimclaw::setup::{SetupConfig, SetupWizard};
/// Synchronous entry point. Loads `.env` files before the Tokio runtime
/// starts so that `std::env::set_var` is safe (no worker threads yet).
fn main() -> anyhow::Result<()> {
let _ = dotenvy::dotenv();
ironclaw::bootstrap::load_ironclaw_env();
optimclaw::bootstrap::load_optimclaw_env();
let result = tokio::runtime::Builder::new_multi_thread()
.enable_all()
@@ -51,7 +51,7 @@ fn main() -> anyhow::Result<()> {
/// Format a top-level error with color and recovery hints.
fn format_top_level_error(err: &anyhow::Error) {
use ironclaw::cli::fmt;
use optimclaw::cli::fmt;
let msg = format!("{err:#}");
eprintln!();
@@ -62,17 +62,17 @@ fn format_top_level_error(err: &anyhow::Error) {
let hint = if lower.contains("database_url")
|| lower.contains("database") && lower.contains("not set")
{
Some("run `ironclaw onboard` or set DATABASE_URL in .env")
Some("run `optimclaw onboard` or set DATABASE_URL in .env")
} else if lower.contains("connection refused") || lower.contains("connect error") {
Some("check that the database server is running")
} else if lower.contains("session") && lower.contains("not found") {
Some("run `ironclaw onboard` to set up authentication")
Some("run `optimclaw onboard` to set up authentication")
} else if lower.contains("secrets_master_key") {
Some("run `ironclaw onboard` or set SECRETS_MASTER_KEY in .env")
Some("run `optimclaw onboard` or set SECRETS_MASTER_KEY in .env")
} else if lower.contains("already running") {
Some("stop the other instance or remove the stale PID file")
} else if lower.contains("onboard") {
Some("run `ironclaw onboard` to complete setup")
Some("run `optimclaw onboard` to complete setup")
} else {
None
};
@@ -94,15 +94,15 @@ async fn async_main() -> anyhow::Result<()> {
}
Some(Command::Config(config_cmd)) => {
init_cli_tracing();
return ironclaw::cli::run_config_command(config_cmd.clone()).await;
return optimclaw::cli::run_config_command(config_cmd.clone()).await;
}
Some(Command::Registry(registry_cmd)) => {
init_cli_tracing();
return ironclaw::cli::run_registry_command(registry_cmd.clone()).await;
return optimclaw::cli::run_registry_command(registry_cmd.clone()).await;
}
Some(Command::Channels(channels_cmd)) => {
init_cli_tracing();
return ironclaw::cli::run_channels_command(
return optimclaw::cli::run_channels_command(
channels_cmd.clone(),
cli.config.as_deref(),
)
@@ -110,7 +110,7 @@ async fn async_main() -> anyhow::Result<()> {
}
Some(Command::Routines(routines_cmd)) => {
init_cli_tracing();
return ironclaw::cli::run_routines_cli(routines_cmd, cli.config.as_deref()).await;
return optimclaw::cli::run_routines_cli(routines_cmd, cli.config.as_deref()).await;
}
Some(Command::Mcp(mcp_cmd)) => {
init_cli_tracing();
@@ -118,7 +118,7 @@ async fn async_main() -> anyhow::Result<()> {
}
Some(Command::Memory(mem_cmd)) => {
init_cli_tracing();
return ironclaw::cli::run_memory_command(mem_cmd).await;
return optimclaw::cli::run_memory_command(mem_cmd).await;
}
Some(Command::Pairing(pairing_cmd)) => {
init_cli_tracing();
@@ -130,26 +130,26 @@ async fn async_main() -> anyhow::Result<()> {
}
Some(Command::Skills(skills_cmd)) => {
init_cli_tracing();
return ironclaw::cli::run_skills_command(skills_cmd.clone(), cli.config.as_deref())
return optimclaw::cli::run_skills_command(skills_cmd.clone(), cli.config.as_deref())
.await;
}
Some(Command::Hooks(hooks_cmd)) => {
init_cli_tracing();
return ironclaw::cli::run_hooks_command(hooks_cmd.clone(), cli.config.as_deref())
return optimclaw::cli::run_hooks_command(hooks_cmd.clone(), cli.config.as_deref())
.await;
}
Some(Command::Logs(logs_cmd)) => {
init_cli_tracing();
return ironclaw::cli::run_logs_command(logs_cmd.clone(), cli.config.as_deref()).await;
return optimclaw::cli::run_logs_command(logs_cmd.clone(), cli.config.as_deref()).await;
}
Some(Command::Models(models_cmd)) => {
init_cli_tracing();
return ironclaw::cli::run_models_command(models_cmd.clone(), cli.config.as_deref())
return optimclaw::cli::run_models_command(models_cmd.clone(), cli.config.as_deref())
.await;
}
Some(Command::Doctor) => {
init_cli_tracing();
return ironclaw::cli::run_doctor_command().await;
return optimclaw::cli::run_doctor_command().await;
}
Some(Command::Status) => {
init_cli_tracing();
@@ -162,8 +162,8 @@ async fn async_main() -> anyhow::Result<()> {
#[cfg(feature = "import")]
Some(Command::Import(import_cmd)) => {
init_cli_tracing();
let config = ironclaw::config::Config::from_env().await?;
return ironclaw::cli::run_import_command(import_cmd, &config).await;
let config = optimclaw::config::Config::from_env().await?;
return optimclaw::cli::run_import_command(import_cmd, &config).await;
}
Some(Command::Worker {
job_id,
@@ -171,7 +171,7 @@ async fn async_main() -> anyhow::Result<()> {
max_iterations,
}) => {
init_worker_tracing();
return ironclaw::worker::run_worker(*job_id, orchestrator_url, *max_iterations).await;
return optimclaw::worker::run_worker(*job_id, orchestrator_url, *max_iterations).await;
}
Some(Command::ClaudeBridge {
job_id,
@@ -180,7 +180,7 @@ async fn async_main() -> anyhow::Result<()> {
model,
}) => {
init_worker_tracing();
return ironclaw::worker::run_claude_bridge(
return optimclaw::worker::run_claude_bridge(
*job_id,
orchestrator_url,
*max_turns,
@@ -198,7 +198,7 @@ async fn async_main() -> anyhow::Result<()> {
.await
.map_err(|e| anyhow::anyhow!("{}", e))?;
config.llm.openai_codex.unwrap_or_else(|| {
use ironclaw::llm::OpenAiCodexConfig;
use optimclaw::llm::OpenAiCodexConfig;
let mut cfg = OpenAiCodexConfig::default();
if let Ok(v) = std::env::var("OPENAI_CODEX_AUTH_URL") {
cfg.auth_endpoint = v;
@@ -215,7 +215,7 @@ async fn async_main() -> anyhow::Result<()> {
cfg
})
};
let mgr = ironclaw::llm::OpenAiCodexSessionManager::new(codex_config)
let mgr = optimclaw::llm::OpenAiCodexSessionManager::new(codex_config)
.map_err(|e| anyhow::anyhow!("{}", e))?;
mgr.device_code_login()
.await
@@ -225,7 +225,7 @@ async fn async_main() -> anyhow::Result<()> {
);
} else {
println!("Specify a provider to authenticate with:");
println!(" ironclaw login --openai-codex (ChatGPT subscription)");
println!(" optimclaw login --openai-codex (ChatGPT subscription)");
}
return Ok(());
}
@@ -262,14 +262,14 @@ async fn async_main() -> anyhow::Result<()> {
}
// ── PID lock (prevent multiple instances) ────────────────────────
let _pid_lock = match ironclaw::bootstrap::PidLock::acquire() {
let _pid_lock = match optimclaw::bootstrap::PidLock::acquire() {
Ok(lock) => Some(lock),
Err(ironclaw::bootstrap::PidLockError::AlreadyRunning { pid }) => {
Err(optimclaw::bootstrap::PidLockError::AlreadyRunning { pid }) => {
anyhow::bail!(
"Another IronClaw instance is already running (PID {}). \
"Another OptimClaw instance is already running (PID {}). \
If this is incorrect, remove the stale PID file: {}",
pid,
ironclaw::bootstrap::pid_lock_path().display()
optimclaw::bootstrap::pid_lock_path().display()
);
}
Err(e) => {
@@ -286,7 +286,7 @@ async fn async_main() -> anyhow::Result<()> {
// Enhanced first-run detection
#[cfg(any(feature = "postgres", feature = "libsql"))]
if !cli.no_onboard
&& let Some(reason) = ironclaw::setup::check_onboard_needed()
&& let Some(reason) = optimclaw::setup::check_onboard_needed()
{
println!("Onboarding needed: {}", reason);
println!();
@@ -307,10 +307,10 @@ async fn async_main() -> anyhow::Result<()> {
let toml_path = cli.config.as_deref();
let config = match Config::from_env_with_toml(toml_path).await {
Ok(c) => c,
Err(ironclaw::error::ConfigError::MissingRequired { key, hint }) => {
Err(optimclaw::error::ConfigError::MissingRequired { key, hint }) => {
anyhow::bail!(
"Configuration error: Missing required setting '{}'. {}. \
Run 'ironclaw onboard' to configure, or set the required environment variables.",
Run 'optimclaw onboard' to configure, or set the required environment variables.",
key,
hint
);
@@ -327,9 +327,9 @@ async fn async_main() -> anyhow::Result<()> {
// Initialize tracing with a reloadable EnvFilter so the gateway can switch
// log levels at runtime without restarting.
let log_level_handle =
ironclaw::channels::web::log_layer::init_tracing(Arc::clone(&log_broadcaster));
optimclaw::channels::web::log_layer::init_tracing(Arc::clone(&log_broadcaster));
tracing::debug!("Starting IronClaw...");
tracing::debug!("Starting OptimClaw...");
tracing::debug!("Loaded configuration for agent: {}", config.agent.name);
tracing::debug!("LLM backend: {}", config.llm.backend);
@@ -350,11 +350,11 @@ async fn async_main() -> anyhow::Result<()> {
// ── Tunnel setup ───────────────────────────────────────────────────
let (config, active_tunnel) = ironclaw::tunnel::start_managed_tunnel(config).await;
let (config, active_tunnel) = optimclaw::tunnel::start_managed_tunnel(config).await;
// ── Orchestrator / container job manager ────────────────────────────
let orch = ironclaw::orchestrator::setup_orchestrator(
let orch = optimclaw::orchestrator::setup_orchestrator(
&config,
&components.llm,
components.db.as_ref(),
@@ -368,12 +368,12 @@ async fn async_main() -> anyhow::Result<()> {
// Derive user-facing warning from docker_status for channel notification
let docker_user_warning: Option<String> = match docker_status {
ironclaw::sandbox::DockerStatus::NotInstalled => Some(
optimclaw::sandbox::DockerStatus::NotInstalled => Some(
"Sandbox is enabled but Docker is not installed -- \
full_job routines will fail until Docker is available."
.to_string(),
),
ironclaw::sandbox::DockerStatus::NotRunning => Some(
optimclaw::sandbox::DockerStatus::NotRunning => Some(
"Sandbox is enabled but Docker is not running -- \
full_job routines will fail until Docker is started."
.to_string(),
@@ -418,7 +418,7 @@ async fn async_main() -> anyhow::Result<()> {
}
// Shared routine engine slot for gateway + generic webhook ingress.
let shared_routine_engine_slot: ironclaw::channels::web::server::RoutineEngineSlot =
let shared_routine_engine_slot: optimclaw::channels::web::server::RoutineEngineSlot =
Arc::new(tokio::sync::RwLock::new(None));
// Collect webhook route fragments; a single WebhookServer hosts them all.
@@ -444,7 +444,7 @@ async fn async_main() -> anyhow::Result<()> {
);
}
if config.channels.wasm_channels_enabled && config.channels.wasm_channels_dir.exists() {
let wasm_result = ironclaw::channels::wasm::setup_wasm_channels(
let wasm_result = optimclaw::channels::wasm::setup_wasm_channels(
&config,
&components.secrets_store,
components.extension_manager.as_ref(),
@@ -491,7 +491,7 @@ async fn async_main() -> anyhow::Result<()> {
// Add HTTP channel if configured and not CLI-only mode.
let mut webhook_server_addr: Option<std::net::SocketAddr> = None;
#[cfg(unix)]
let mut http_channel_state: Option<Arc<ironclaw::channels::HttpChannelState>> = None;
let mut http_channel_state: Option<Arc<optimclaw::channels::HttpChannelState>> = None;
if !cli.cli_only
&& let Some(ref http_config) = config.channels.http
{
@@ -567,7 +567,7 @@ async fn async_main() -> anyhow::Result<()> {
// Lazy scheduler slot — filled after Agent::new creates the Scheduler.
// Allows CreateJobTool to dispatch local jobs via the Scheduler even though
// the Scheduler is created after tools are registered (chicken-and-egg).
let scheduler_slot: ironclaw::tools::builtin::SchedulerSlot =
let scheduler_slot: optimclaw::tools::builtin::SchedulerSlot =
Arc::new(tokio::sync::RwLock::new(None));
// Register job tools (sandbox deps auto-injected when container_job_manager is available)
@@ -589,7 +589,7 @@ async fn async_main() -> anyhow::Result<()> {
// ── Gateway channel ────────────────────────────────────────────────
let mut gateway_url: Option<String> = None;
let mut sse_manager: Option<std::sync::Arc<ironclaw::channels::web::sse::SseManager>> = None;
let mut sse_manager: Option<std::sync::Arc<optimclaw::channels::web::sse::SseManager>> = None;
if let Some(ref gw_config) = config.channels.gateway {
let mut gw = GatewayChannel::new(gw_config.clone(), config.owner_id.clone());
gw = gw.with_llm_provider(Arc::clone(&components.llm));
@@ -598,10 +598,10 @@ async fn async_main() -> anyhow::Result<()> {
}
// Create per-user workspace pool for multi-user mode.
if let Some(ref db) = components.db {
let emb_cache_config = ironclaw::workspace::EmbeddingCacheConfig {
let emb_cache_config = optimclaw::workspace::EmbeddingCacheConfig {
max_entries: config.embeddings.cache_size,
};
let pool = Arc::new(ironclaw::channels::web::server::WorkspacePool::new(
let pool = Arc::new(optimclaw::channels::web::server::WorkspacePool::new(
Arc::clone(db),
components.embeddings.clone(),
emb_cache_config,
@@ -639,7 +639,7 @@ async fn async_main() -> anyhow::Result<()> {
// so the owner appears in the Users admin panel immediately.
if let Ok(false) = d.has_any_users().await {
let now = chrono::Utc::now();
let user = ironclaw::db::UserRecord {
let user = optimclaw::db::UserRecord {
id: config.owner_id.clone(),
email: None,
display_name: config.owner_id.clone(),
@@ -658,7 +658,7 @@ async fn async_main() -> anyhow::Result<()> {
tracing::warn!("Failed to bootstrap admin user: {}", e);
}
} else {
use ironclaw::channels::web::auth::hash_token;
use optimclaw::channels::web::auth::hash_token;
let hash = hash_token(auth_token);
let prefix = if auth_token.len() >= 8 {
&auth_token[..8]
@@ -695,7 +695,7 @@ async fn async_main() -> anyhow::Result<()> {
let active_model = components.llm.model_name().to_string();
let mut enabled = channel_names.clone();
enabled.push("gateway".into());
gw = gw.with_active_config(ironclaw::channels::web::server::ActiveConfigSnapshot {
gw = gw.with_active_config(optimclaw::channels::web::server::ActiveConfigSnapshot {
llm_backend: config.llm.backend.to_string(),
llm_model: active_model,
enabled_channels: enabled,
@@ -770,7 +770,7 @@ async fn async_main() -> anyhow::Result<()> {
.map(|c| c.model_name().to_string());
if config.channels.cli.enabled && cli.message.is_none() {
let boot_info = ironclaw::boot_screen::BootInfo {
let boot_info = optimclaw::boot_screen::BootInfo {
version: env!("CARGO_PKG_VERSION").to_string(),
agent_name: config.agent.name.clone(),
llm_backend: config.llm.backend.to_string(),
@@ -805,7 +805,7 @@ async fn async_main() -> anyhow::Result<()> {
tunnel_provider: active_tunnel.as_ref().map(|t| t.name().to_string()),
startup_elapsed: Some(startup_start.elapsed()),
};
ironclaw::boot_screen::print_boot_screen(&boot_info);
optimclaw::boot_screen::print_boot_screen(&boot_info);
}
// ── Run the agent ──────────────────────────────────────────────────
@@ -899,10 +899,10 @@ async fn async_main() -> anyhow::Result<()> {
// Capture db reference for SIGHUP handler before it's moved into AgentDeps (Unix only)
#[cfg(unix)]
let sighup_settings_store: Option<Arc<dyn ironclaw::db::SettingsStore>> = components
let sighup_settings_store: Option<Arc<dyn optimclaw::db::SettingsStore>> = components
.db
.as_ref()
.map(|db| Arc::clone(db) as Arc<dyn ironclaw::db::SettingsStore>);
.map(|db| Arc::clone(db) as Arc<dyn optimclaw::db::SettingsStore>);
let deps = AgentDeps {
owner_id: config.owner_id.clone(),
@@ -921,23 +921,23 @@ async fn async_main() -> anyhow::Result<()> {
sse_tx: sse_manager,
http_interceptor,
transcription: config.transcription.create_provider().map(|p| {
Arc::new(ironclaw::llm::transcription::TranscriptionMiddleware::new(
Arc::new(optimclaw::llm::transcription::TranscriptionMiddleware::new(
p,
))
}),
document_extraction: Some(Arc::new(
ironclaw::document_extraction::DocumentExtractionMiddleware::new(),
optimclaw::document_extraction::DocumentExtractionMiddleware::new(),
)),
sandbox_readiness: if !config.sandbox.enabled {
ironclaw::agent::routine_engine::SandboxReadiness::DisabledByConfig
optimclaw::agent::routine_engine::SandboxReadiness::DisabledByConfig
} else if docker_status.is_ok() {
ironclaw::agent::routine_engine::SandboxReadiness::Available
optimclaw::agent::routine_engine::SandboxReadiness::Available
} else {
ironclaw::agent::routine_engine::SandboxReadiness::DockerUnavailable
optimclaw::agent::routine_engine::SandboxReadiness::DockerUnavailable
},
builder: components.builder,
llm_backend: config.llm.backend.clone(),
tenant_rates: Arc::new(ironclaw::tenant::TenantRateRegistry::new(
tenant_rates: Arc::new(optimclaw::tenant::TenantRateRegistry::new(
config.agent.max_llm_concurrent_per_user.unwrap_or(4),
config.agent.max_jobs_concurrent_per_user.unwrap_or(3),
)),
@@ -1029,7 +1029,7 @@ async fn async_main() -> anyhow::Result<()> {
{
// Thread-safe: Uses INJECTED_VARS mutex instead of unsafe std::env::set_var
// Config::from_env() will read from the overlay via optional_env()
ironclaw::config::inject_single_var(
optimclaw::config::inject_single_var(
"HTTP_WEBHOOK_SECRET",
webhook_secret.expose(),
);
@@ -1040,9 +1040,9 @@ async fn async_main() -> anyhow::Result<()> {
// Reload config (now with secrets injected into environment)
let new_config = match &sighup_settings_store_clone {
Some(store) => {
ironclaw::config::Config::from_db(store.as_ref(), &sighup_owner_id).await
optimclaw::config::Config::from_db(store.as_ref(), &sighup_owner_id).await
}
None => ironclaw::config::Config::from_env().await,
None => optimclaw::config::Config::from_env().await,
};
let new_config = match new_config {
@@ -1158,7 +1158,7 @@ async fn async_main() -> anyhow::Result<()> {
// 5s is generous but avoids the message being lost on slow startups.
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
tracing::debug!("Sending sandbox-unavailable warning to connected channels");
let response = ironclaw::channels::OutgoingResponse {
let response = optimclaw::channels::OutgoingResponse {
content: format!("Warning: {warning}"),
thread_id: None,
attachments: Vec::new(),
+1 -1
View File
@@ -25,7 +25,7 @@ use crate::worker::api::{
CompletionReport, CredentialResponse, JobDescription, ProxyCompletionRequest,
ProxyCompletionResponse, ProxyToolCompletionRequest, ProxyToolCompletionResponse, StatusUpdate,
};
use ironclaw_common::AppEvent;
use optimclaw_common::AppEvent;
/// A follow-up prompt queued for a Claude Code bridge.
#[derive(Debug, Clone, Serialize, Deserialize)]
+16 -16
View File
@@ -11,7 +11,7 @@ use chrono::{DateTime, Utc};
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::error::OrchestratorError;
use crate::orchestrator::auth::{CredentialGrant, TokenStore};
use crate::sandbox::connect_docker;
@@ -19,7 +19,7 @@ use crate::sandbox::connect_docker;
/// Which mode a sandbox container runs in.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JobMode {
/// Standard IronClaw worker with proxied LLM calls.
/// Standard OptimClaw worker with proxied LLM calls.
Worker,
/// Claude Code bridge that spawns the `claude` CLI directly.
ClaudeCode,
@@ -71,7 +71,7 @@ pub struct ContainerJobConfig {
impl Default for ContainerJobConfig {
fn default() -> Self {
Self {
image: "ironclaw-worker:latest".to_string(),
image: "optimclaw-worker:latest".to_string(),
memory_limit_mb: 2048,
cpu_shares: 1024,
orchestrator_port: 50051,
@@ -132,7 +132,7 @@ pub struct CompletionResult {
pub message: Option<String>,
}
/// Validate that a project directory is under `~/.ironclaw/projects/`.
/// Validate that a project directory is under `~/.optimclaw/projects/`.
///
/// Returns the canonicalized path if valid. Creates the base directory if
/// it doesn't exist (so the prefix check always runs).
@@ -142,7 +142,7 @@ pub struct CompletionResult {
/// There is a time-of-check/time-of-use gap between `canonicalize()` here
/// and the actual Docker `binds.push()` in the caller. In a multi-tenant
/// system a malicious actor could swap a symlink after validation. This is
/// acceptable in IronClaw's single-tenant design where the user controls
/// acceptable in OptimClaw's single-tenant design where the user controls
/// the filesystem.
fn validate_bind_mount_path(
dir: &std::path::Path,
@@ -159,7 +159,7 @@ fn validate_bind_mount_path(
),
})?;
let projects_base = ironclaw_base_dir().join("projects");
let projects_base = optimclaw_base_dir().join("projects");
if !projects_base.is_absolute() {
return Err(OrchestratorError::ContainerCreationFailed {
@@ -318,17 +318,17 @@ impl ContainerJobManager {
);
let mut env_vec = vec![
format!("IRONCLAW_WORKER_TOKEN={}", token),
format!("IRONCLAW_JOB_ID={}", job_id),
format!("IRONCLAW_ORCHESTRATOR_URL={}", orchestrator_url),
format!("OPTIMCLAW_WORKER_TOKEN={}", token),
format!("OPTIMCLAW_JOB_ID={}", job_id),
format!("OPTIMCLAW_ORCHESTRATOR_URL={}", orchestrator_url),
];
// Build volume mounts (validate project_dir stays within ~/.ironclaw/projects/)
// Build volume mounts (validate project_dir stays within ~/.optimclaw/projects/)
let mut binds = Vec::new();
if let Some(ref dir) = project_dir {
let canonical = validate_bind_mount_path(dir, job_id)?;
binds.push(format!("{}:/workspace:rw", canonical.display()));
env_vec.push("IRONCLAW_WORKSPACE=/workspace".to_string());
env_vec.push("OPTIMCLAW_WORKSPACE=/workspace".to_string());
}
// Claude Code mode: auth + tool allowlist.
@@ -402,9 +402,9 @@ impl ContainerJobManager {
// Add Docker labels for reaper identification and orphan detection
let mut labels = std::collections::HashMap::new();
labels.insert("ironclaw.job_id".to_string(), job_id.to_string());
labels.insert("optimclaw.job_id".to_string(), job_id.to_string());
labels.insert(
"ironclaw.created_at".to_string(),
"optimclaw.created_at".to_string(),
chrono::Utc::now().to_rfc3339(),
);
@@ -420,8 +420,8 @@ impl ContainerJobManager {
};
let container_name = match mode {
JobMode::Worker => format!("ironclaw-worker-{}", job_id),
JobMode::ClaudeCode => format!("ironclaw-claude-{}", job_id),
JobMode::Worker => format!("optimclaw-worker-{}", job_id),
JobMode::ClaudeCode => format!("optimclaw-claude-{}", job_id),
};
let options = CreateContainerOptions {
name: container_name,
@@ -630,7 +630,7 @@ mod tests {
#[test]
fn test_validate_bind_mount_valid_path() {
let base = crate::bootstrap::compute_ironclaw_base_dir().join("projects");
let base = crate::bootstrap::compute_optimclaw_base_dir().join("projects");
std::fs::create_dir_all(&base).unwrap();
let test_dir = base.join("test_validate_bind");
+1 -1
View File
@@ -49,7 +49,7 @@ use uuid::Uuid;
use crate::db::Database;
use crate::llm::LlmProvider;
use crate::secrets::SecretsStore;
use ironclaw_common::AppEvent;
use optimclaw_common::AppEvent;
/// Resolve the orchestrator port from the `ORCHESTRATOR_PORT` environment
/// variable, falling back to 50051.
+40 -40
View File
@@ -1,13 +1,13 @@
//! Orphaned Docker container cleanup.
//!
//! The SandboxReaper periodically scans Docker for IronClaw-labeled containers
//! The SandboxReaper periodically scans Docker for OptimClaw-labeled containers
//! and cleans up those whose corresponding jobs are not active.
//!
//! **Problem:** If the agent process crashes between container creation and cleanup,
//! containers are orphaned indefinitely.
//!
//! **Solution:** Background reaper task that:
//! 1. Scans Docker for containers with the `ironclaw.job_id` label
//! 1. Scans Docker for containers with the `optimclaw.job_id` label
//! 2. Checks if each job is active in the ContextManager
//! 3. Cleans up containers with inactive/missing jobs
@@ -38,7 +38,7 @@ impl Default for ReaperConfig {
Self {
scan_interval: Duration::from_secs(300),
orphan_threshold: Duration::from_secs(600),
container_label: "ironclaw.job_id".to_string(),
container_label: "optimclaw.job_id".to_string(),
}
}
}
@@ -88,7 +88,7 @@ impl SandboxReaper {
}
async fn scan_and_reap(&self) {
let containers = match self.list_ironclaw_containers().await {
let containers = match self.list_optimclaw_containers().await {
Ok(c) => c,
Err(e) => {
tracing::error!(error = %e, "Reaper: failed to list Docker containers");
@@ -145,10 +145,10 @@ impl SandboxReaper {
}
}
/// List all IronClaw-managed containers from Docker.
/// List all OptimClaw-managed containers from Docker.
///
/// Returns tuples of (container_id, job_id, created_at).
async fn list_ironclaw_containers(
async fn list_optimclaw_containers(
&self,
) -> Result<Vec<(String, Uuid, DateTime<Utc>)>, bollard::errors::Error> {
use bollard::container::ListContainersOptions;
@@ -183,7 +183,7 @@ impl SandboxReaper {
tracing::warn!(
container_id = %&container_id[..12.min(container_id.len())],
label_key = %&self.config.container_label,
"Reaper: ironclaw container missing valid job_id label"
"Reaper: optimclaw container missing valid job_id label"
);
continue;
}
@@ -191,7 +191,7 @@ impl SandboxReaper {
// Parse created_at from label (set by us at creation time); fall back to Docker timestamp
let created_at = match labels
.get("ironclaw.created_at")
.get("optimclaw.created_at")
.and_then(|s| DateTime::parse_from_rfc3339(s).ok())
.map(|dt| dt.with_timezone(&Utc))
.or_else(|| {
@@ -418,20 +418,20 @@ mod tests {
fn parse_container_labels_extracts_job_id_and_timestamp() {
let mut labels = HashMap::new();
let job_id = Uuid::new_v4();
labels.insert("ironclaw.job_id".to_string(), job_id.to_string());
labels.insert("optimclaw.job_id".to_string(), job_id.to_string());
labels.insert(
"ironclaw.created_at".to_string(),
"optimclaw.created_at".to_string(),
"2024-01-15T10:30:45+00:00".to_string(),
);
// Verify parsing works
let parsed_id: Option<Uuid> = labels
.get("ironclaw.job_id")
.get("optimclaw.job_id")
.and_then(|s| s.parse::<Uuid>().ok());
assert_eq!(parsed_id, Some(job_id));
let parsed_time = labels
.get("ironclaw.created_at")
.get("optimclaw.created_at")
.and_then(|s| DateTime::parse_from_rfc3339(s).ok());
assert!(parsed_time.is_some());
}
@@ -441,7 +441,7 @@ mod tests {
fn missing_job_id_label_is_skipped() {
let labels: HashMap<String, String> = HashMap::new();
let job_id: Option<Uuid> = labels
.get("ironclaw.job_id")
.get("optimclaw.job_id")
.and_then(|s| s.parse::<Uuid>().ok());
assert_eq!(job_id, None);
}
@@ -451,12 +451,12 @@ mod tests {
fn malformed_timestamp_fallback_works() {
let mut labels: HashMap<String, String> = HashMap::new();
labels.insert(
"ironclaw.created_at".to_string(),
"optimclaw.created_at".to_string(),
"invalid-date".to_string(),
);
let parsed_time = labels
.get("ironclaw.created_at")
.get("optimclaw.created_at")
.and_then(|s| DateTime::parse_from_rfc3339(s).ok());
assert!(
parsed_time.is_none(),
@@ -556,7 +556,7 @@ mod tests {
Duration::from_secs(600),
"Orphan threshold should be 10 min"
);
assert_eq!(cfg.container_label, "ironclaw.job_id");
assert_eq!(cfg.container_label, "optimclaw.job_id");
}
// Test: reaper config is customizable
@@ -663,24 +663,24 @@ mod tests {
// ================================================================
//
// These tests verify the reaper works with actual Docker containers.
// They require Docker to be running and the IRONCLAW_E2E_DOCKER_TESTS
// They require Docker to be running and the OPTIMCLAW_E2E_DOCKER_TESTS
// environment variable to be set (to avoid running them in CI by default).
//
// Run with: IRONCLAW_E2E_DOCKER_TESTS=1 cargo test orchestrator::reaper::e2e_tests --lib -- --nocapture
// Run with: OPTIMCLAW_E2E_DOCKER_TESTS=1 cargo test orchestrator::reaper::e2e_tests --lib -- --nocapture
#[cfg(all(test, not(target_env = "msvc")))]
mod e2e_tests {
use super::*;
fn should_run_e2e() -> bool {
std::env::var("IRONCLAW_E2E_DOCKER_TESTS").is_ok()
std::env::var("OPTIMCLAW_E2E_DOCKER_TESTS").is_ok()
}
/// Test that reaper can list containers with IronClaw labels
/// Test that reaper can list containers with OptimClaw labels
#[tokio::test]
async fn e2e_reaper_lists_ironclaw_containers() {
async fn e2e_reaper_lists_optimclaw_containers() {
if !should_run_e2e() {
eprintln!("Skipping e2e test (set IRONCLAW_E2E_DOCKER_TESTS=1 to run)");
eprintln!("Skipping e2e test (set OPTIMCLAW_E2E_DOCKER_TESTS=1 to run)");
return;
}
@@ -693,17 +693,17 @@ mod tests {
}
};
// Create a test container with IronClaw labels
// Create a test container with OptimClaw labels
let job_id = Uuid::new_v4();
let test_name = format!("ironclaw-reaper-test-{}", &job_id.to_string()[..8]);
let test_name = format!("optimclaw-reaper-test-{}", &job_id.to_string()[..8]);
let job_id_str = job_id.to_string();
let created_at_str = (Utc::now() - chrono::Duration::hours(1)).to_rfc3339();
let mut labels_str: std::collections::HashMap<&str, &str> =
std::collections::HashMap::new();
labels_str.insert("ironclaw.job_id", &job_id_str);
labels_str.insert("ironclaw.created_at", &created_at_str);
labels_str.insert("optimclaw.job_id", &job_id_str);
labels_str.insert("optimclaw.created_at", &created_at_str);
let config = bollard::container::CreateContainerOptions {
name: test_name.as_str(),
@@ -746,11 +746,11 @@ mod tests {
let labels = inspect.config.and_then(|c| c.labels).unwrap_or_default();
assert!(
labels.contains_key("ironclaw.job_id"),
"Container should have ironclaw.job_id label"
labels.contains_key("optimclaw.job_id"),
"Container should have optimclaw.job_id label"
);
assert_eq!(
labels.get("ironclaw.job_id").map(|s| s.as_str()),
labels.get("optimclaw.job_id").map(|s| s.as_str()),
Some(job_id.to_string().as_str()),
"job_id label should match"
);
@@ -766,7 +766,7 @@ mod tests {
#[tokio::test]
async fn e2e_reaper_removes_orphaned_containers() {
if !should_run_e2e() {
eprintln!("Skipping e2e test (set IRONCLAW_E2E_DOCKER_TESTS=1 to run)");
eprintln!("Skipping e2e test (set OPTIMCLAW_E2E_DOCKER_TESTS=1 to run)");
return;
}
@@ -781,14 +781,14 @@ mod tests {
// Create a fake job ID that won't exist in context manager
let orphaned_job_id = Uuid::new_v4();
let test_name = format!("ironclaw-orphan-test-{}", &orphaned_job_id.to_string()[..8]);
let test_name = format!("optimclaw-orphan-test-{}", &orphaned_job_id.to_string()[..8]);
let job_id_str = orphaned_job_id.to_string();
let created_at_str = (Utc::now() - chrono::Duration::hours(2)).to_rfc3339();
let mut labels: std::collections::HashMap<&str, &str> =
std::collections::HashMap::new();
labels.insert("ironclaw.job_id", &job_id_str);
labels.insert("ironclaw.created_at", &created_at_str);
labels.insert("optimclaw.job_id", &job_id_str);
labels.insert("optimclaw.created_at", &created_at_str);
let config = bollard::container::CreateContainerOptions {
name: test_name.as_str(),
@@ -863,7 +863,7 @@ mod tests {
#[tokio::test]
async fn e2e_reaper_respects_age_threshold() {
if !should_run_e2e() {
eprintln!("Skipping e2e test (set IRONCLAW_E2E_DOCKER_TESTS=1 to run)");
eprintln!("Skipping e2e test (set OPTIMCLAW_E2E_DOCKER_TESTS=1 to run)");
return;
}
@@ -884,21 +884,21 @@ mod tests {
let old_time_str = (Utc::now() - chrono::Duration::hours(2)).to_rfc3339();
let mut old_labels: std::collections::HashMap<&str, &str> =
std::collections::HashMap::new();
old_labels.insert("ironclaw.job_id", &old_id_str);
old_labels.insert("ironclaw.created_at", &old_time_str);
old_labels.insert("optimclaw.job_id", &old_id_str);
old_labels.insert("optimclaw.created_at", &old_time_str);
// New container (created 1 minute ago, within threshold)
let new_id_str = new_job_id.to_string();
let new_time_str = (Utc::now() - chrono::Duration::minutes(1)).to_rfc3339();
let mut new_labels: std::collections::HashMap<&str, &str> =
std::collections::HashMap::new();
new_labels.insert("ironclaw.job_id", &new_id_str);
new_labels.insert("ironclaw.created_at", &new_time_str);
new_labels.insert("optimclaw.job_id", &new_id_str);
new_labels.insert("optimclaw.created_at", &new_time_str);
let mut containers_to_cleanup = Vec::new();
// Create old container
let old_name = format!("ironclaw-age-old-{}", &old_job_id.to_string()[..8]);
let old_name = format!("optimclaw-age-old-{}", &old_job_id.to_string()[..8]);
if let Ok(r) = docker
.create_container(
Some(bollard::container::CreateContainerOptions {
@@ -918,7 +918,7 @@ mod tests {
}
// Create new container
let new_name = format!("ironclaw-age-new-{}", &new_job_id.to_string()[..8]);
let new_name = format!("optimclaw-age-new-{}", &new_job_id.to_string()[..8]);
if let Ok(r) = docker
.create_container(
Some(bollard::container::CreateContainerOptions {
+1 -1
View File
@@ -1,7 +1,7 @@
//! DM pairing for channels.
//!
//! Gates DMs from unknown senders. Only approved senders can message the agent.
//! Unknown senders receive a pairing code and must be approved via `ironclaw pairing approve`.
//! Unknown senders receive a pairing code and must be approved via `optimclaw pairing approve`.
//!
//! OpenClaw reference: src/pairing/pairing-store.ts
+4 -4
View File
@@ -1,6 +1,6 @@
//! Pairing store: pending requests and allowFrom list.
//!
//! Stored in ~/.ironclaw/{channel}-pairing.json and {channel}-allowFrom.json.
//! Stored in ~/.optimclaw/{channel}-pairing.json and {channel}-allowFrom.json.
use std::collections::HashSet;
use std::fs;
@@ -13,7 +13,7 @@ use rand::Rng;
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
const PAIRING_CODE_LENGTH: usize = 8;
const PAIRING_ALPHABET: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
@@ -76,7 +76,7 @@ struct AllowFromStoreFile {
}
fn default_pairing_dir() -> PathBuf {
ironclaw_base_dir()
optimclaw_base_dir()
}
fn safe_channel_key(channel: &str) -> Result<String, PairingStoreError> {
@@ -176,7 +176,7 @@ pub struct PairingStore {
}
impl PairingStore {
/// Create a new pairing store using default directory (~/.ironclaw).
/// Create a new pairing store using default directory (~/.optimclaw).
pub fn new() -> Self {
Self {
base_dir: default_pairing_dir(),
+1 -1
View File
@@ -1,6 +1,6 @@
//! Embedded registry catalog compiled into the binary at build time.
//!
//! When IronClaw is distributed as a pre-built binary without a source tree,
//! When OptimClaw is distributed as a pre-built binary without a source tree,
//! the `registry/` directory is unavailable. This module provides the same
//! manifest data via `include_str!` from a JSON blob generated by `build.rs`.
+14 -14
View File
@@ -5,7 +5,7 @@ use std::path::{Component, Path, PathBuf};
use tokio::fs;
use crate::bootstrap::ironclaw_base_dir;
use crate::bootstrap::optimclaw_base_dir;
use crate::registry::catalog::RegistryError;
use crate::registry::manifest::{BundleDefinition, ExtensionManifest, ManifestKind, SourceSpec};
@@ -29,7 +29,7 @@ fn should_attempt_source_fallback(err: &RegistryError) -> bool {
// Version-pinned URLs (`releases/download/vX.Y.Z/`) point to an immutable
// asset; a mismatch there is genuinely suspicious and remains a hard block.
RegistryError::ChecksumMismatch { url, .. } => {
url.contains("github.com/nearai/ironclaw/releases/latest/")
url.contains("github.com/nearai/optimclaw/releases/latest/")
}
// Never fall back for these — they signal a structural problem or a
// deliberate "already done" state, not a transient artifact issue.
@@ -202,9 +202,9 @@ pub struct InstallOutcome {
pub struct RegistryInstaller {
/// Root of the repo (parent of `registry/`), used to resolve `source.dir`.
repo_root: PathBuf,
/// Directory for installed tools (`~/.ironclaw/tools/`).
/// Directory for installed tools (`~/.optimclaw/tools/`).
tools_dir: PathBuf,
/// Directory for installed channels (`~/.ironclaw/channels/`).
/// Directory for installed channels (`~/.optimclaw/channels/`).
channels_dir: PathBuf,
}
@@ -219,7 +219,7 @@ impl RegistryInstaller {
/// Default installer using standard paths.
pub fn with_defaults(repo_root: PathBuf) -> Self {
let base_dir = ironclaw_base_dir();
let base_dir = optimclaw_base_dir();
Self {
repo_root,
tools_dir: base_dir.join("tools"),
@@ -264,7 +264,7 @@ impl RegistryInstaller {
.map_err(RegistryError::Io)?;
// Use manifest.name for installed filenames so discovery, auth, and
// CLI commands (`ironclaw tool auth <name>`) all agree on the stem.
// CLI commands (`optimclaw tool auth <name>`) all agree on the stem.
let target_wasm = target_dir.join(format!("{}.wasm", manifest.name));
// Check if already exists
@@ -593,7 +593,7 @@ impl RegistryInstaller {
let mut auth_hints = Vec::new();
if let Some(shared) = &bundle.shared_auth {
auth_hints.push(format!(
"Bundle uses shared auth '{}'. Run `ironclaw tool auth <any-member>` to authenticate all members.",
"Bundle uses shared auth '{}'. Run `optimclaw tool auth <any-member>` to authenticate all members.",
shared
));
}
@@ -851,8 +851,8 @@ mod tests {
fn test_installer_creation() {
let installer = RegistryInstaller::new(
PathBuf::from("/repo"),
PathBuf::from("/home/.ironclaw/tools"),
PathBuf::from("/home/.ironclaw/channels"),
PathBuf::from("/home/.optimclaw/tools"),
PathBuf::from("/home/.optimclaw/channels"),
);
assert_eq!(installer.repo_root, PathBuf::from("/repo"));
}
@@ -914,7 +914,7 @@ mod tests {
"demo",
"tools-src/demo",
Some(
"http://github.com/nearai/ironclaw/releases/latest/download/demo.wasm".to_string(),
"http://github.com/nearai/optimclaw/releases/latest/download/demo.wasm".to_string(),
),
None,
);
@@ -967,7 +967,7 @@ mod tests {
"demo",
"tools-src/demo",
Some(
"https://github.com/nearai/ironclaw/releases/latest/download/demo-wasm32-wasip2.tar.gz".to_string(),
"https://github.com/nearai/optimclaw/releases/latest/download/demo-wasm32-wasip2.tar.gz".to_string(),
),
None, // sha256 = null
);
@@ -984,7 +984,7 @@ mod tests {
#[test]
fn test_should_attempt_source_fallback_policy() {
let download = RegistryError::DownloadFailed {
url: "https://github.com/nearai/ironclaw/releases/latest/download/demo.wasm"
url: "https://github.com/nearai/optimclaw/releases/latest/download/demo.wasm"
.to_string(),
reason: "http status 404".to_string(),
};
@@ -1152,7 +1152,7 @@ mod tests {
#[test]
fn test_source_fallback_on_latest_url_mismatch() {
let latest_mismatch = RegistryError::ChecksumMismatch {
url: "https://github.com/nearai/ironclaw/releases/latest/download/github-wasm32-wasip2.tar.gz".to_string(),
url: "https://github.com/nearai/optimclaw/releases/latest/download/github-wasm32-wasip2.tar.gz".to_string(),
expected_sha256: "aaa".to_string(),
actual_sha256: "bbb".to_string(),
};
@@ -1162,7 +1162,7 @@ mod tests {
);
let pinned_mismatch = RegistryError::ChecksumMismatch {
url: "https://github.com/nearai/ironclaw/releases/download/v0.7.0/github-0.2.0-wasm32-wasip2.tar.gz".to_string(),
url: "https://github.com/nearai/optimclaw/releases/download/v0.7.0/github-0.2.0-wasm32-wasip2.tar.gz".to_string(),
expected_sha256: "aaa".to_string(),
actual_sha256: "bbb".to_string(),
};
+1 -1
View File
@@ -402,7 +402,7 @@ mod tests {
},
"artifacts": {
"wasm32-wasip2": {
"url": "https://github.com/nearai/ironclaw/releases/latest/download/gmail-wasm32-wasip2.tar.gz",
"url": "https://github.com/nearai/optimclaw/releases/latest/download/gmail-wasm32-wasip2.tar.gz",
"sha256": null
}
},

Some files were not shown because too many files have changed in this diff Show More