mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-29 17:09:31 +00:00
feat: Import OpenClaw memory, history and settings (#903)
* feat: Import OpenClaw memory, history and settings * review fixes * fix: address remaining code quality issues 1. Remove dead import_conversation() function - replaced by import_conversation_atomic() 2. Improve non-UTF-8 filename handling in list_agent_dbs() - log warning instead of silent 'unknown' 3. Remove emojis from CLI output per project style guide Co-Authored-By: Claude Haiku 4.5 <[email protected]> --------- Co-authored-by: Claude Haiku 4.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Haiku 4.5
parent
b0214fef41
commit
26068db24b
@@ -0,0 +1,162 @@
|
||||
//! Import command for migrating data from other AI systems.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::Subcommand;
|
||||
|
||||
#[cfg(feature = "import")]
|
||||
use crate::import::ImportOptions;
|
||||
#[cfg(feature = "import")]
|
||||
use crate::import::openclaw::OpenClawImporter;
|
||||
|
||||
/// Import data from other AI systems.
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum ImportCommand {
|
||||
/// Import from OpenClaw (memory, history, settings, credentials)
|
||||
#[cfg(feature = "import")]
|
||||
Openclaw {
|
||||
/// Path to OpenClaw directory (default: ~/.openclaw)
|
||||
#[arg(long)]
|
||||
path: Option<PathBuf>,
|
||||
|
||||
/// Dry-run mode: show what would be imported without writing
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
|
||||
/// Re-embed memory if dimensions don't match target provider
|
||||
#[arg(long)]
|
||||
re_embed: bool,
|
||||
|
||||
/// User ID for imported data (default: 'default')
|
||||
#[arg(long)]
|
||||
user_id: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Run an import command.
|
||||
#[cfg(feature = "import")]
|
||||
pub async fn run_import_command(
|
||||
cmd: &ImportCommand,
|
||||
config: &crate::config::Config,
|
||||
) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
ImportCommand::Openclaw {
|
||||
path,
|
||||
dry_run,
|
||||
re_embed,
|
||||
user_id,
|
||||
} => run_import_openclaw(config, path.clone(), *dry_run, *re_embed, user_id.clone()).await,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the OpenClaw import.
|
||||
#[cfg(feature = "import")]
|
||||
async fn run_import_openclaw(
|
||||
config: &crate::config::Config,
|
||||
openclaw_path: Option<PathBuf>,
|
||||
dry_run: bool,
|
||||
re_embed: bool,
|
||||
user_id: Option<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
use secrecy::SecretString;
|
||||
|
||||
// Determine OpenClaw path
|
||||
let openclaw_path = if let Some(path) = openclaw_path {
|
||||
path
|
||||
} else if let Some(path) = OpenClawImporter::detect() {
|
||||
path
|
||||
} else {
|
||||
let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
|
||||
PathBuf::from(home).join(".openclaw")
|
||||
};
|
||||
|
||||
let user_id = user_id.unwrap_or_else(|| "default".to_string());
|
||||
|
||||
println!("🔍 OpenClaw Import");
|
||||
println!(" Path: {}", openclaw_path.display());
|
||||
println!(" User: {}", user_id);
|
||||
if dry_run {
|
||||
println!(" Mode: DRY RUN (no data will be written)");
|
||||
}
|
||||
println!();
|
||||
|
||||
// Initialize database
|
||||
let db = crate::db::connect_from_config(&config.database)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Failed to initialize database: {}", e))?;
|
||||
|
||||
// Initialize secrets store with master key from env or keychain
|
||||
let secrets_crypto = if let Ok(master_key_hex) = std::env::var("SECRETS_MASTER_KEY") {
|
||||
Arc::new(
|
||||
crate::secrets::SecretsCrypto::new(SecretString::from(master_key_hex))
|
||||
.map_err(|e| anyhow::anyhow!("Failed to initialize secrets: {}", e))?,
|
||||
)
|
||||
} else {
|
||||
match crate::secrets::keychain::get_master_key().await {
|
||||
Ok(key_bytes) => {
|
||||
let key_hex: String = key_bytes.iter().map(|b| format!("{:02x}", b)).collect();
|
||||
Arc::new(
|
||||
crate::secrets::SecretsCrypto::new(SecretString::from(key_hex))
|
||||
.map_err(|e| anyhow::anyhow!("Failed to initialize secrets: {}", e))?,
|
||||
)
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"No secrets master key found. Set SECRETS_MASTER_KEY env var or run 'ironclaw onboard' first."
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let secrets: Arc<dyn crate::secrets::SecretsStore> = Arc::new(
|
||||
crate::secrets::InMemorySecretsStore::new(secrets_crypto.clone()),
|
||||
);
|
||||
|
||||
// Initialize workspace
|
||||
let workspace = crate::workspace::Workspace::new_with_db(user_id.clone(), db.clone());
|
||||
|
||||
let opts = ImportOptions {
|
||||
openclaw_path,
|
||||
dry_run,
|
||||
re_embed,
|
||||
user_id,
|
||||
};
|
||||
|
||||
let importer = OpenClawImporter::new(db, workspace, secrets, opts);
|
||||
let stats = importer.import().await?;
|
||||
|
||||
// Print results
|
||||
println!("Import Complete");
|
||||
println!();
|
||||
println!("Summary:");
|
||||
println!(" Documents: {}", stats.documents);
|
||||
println!(" Chunks: {}", stats.chunks);
|
||||
println!(" Conversations: {}", stats.conversations);
|
||||
println!(" Messages: {}", stats.messages);
|
||||
println!(" Settings: {}", stats.settings);
|
||||
println!(" Secrets: {}", stats.secrets);
|
||||
if stats.skipped > 0 {
|
||||
println!(" Skipped: {}", stats.skipped);
|
||||
}
|
||||
if stats.re_embed_queued > 0 {
|
||||
println!(" Re-embed queued: {}", stats.re_embed_queued);
|
||||
}
|
||||
println!();
|
||||
println!("Total imported: {}", stats.total_imported());
|
||||
|
||||
if dry_run {
|
||||
println!();
|
||||
println!("[DRY RUN] No data was written.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "import"))]
|
||||
pub async fn run_import_command(
|
||||
_cmd: &ImportCommand,
|
||||
_config: &crate::config::Config,
|
||||
) -> anyhow::Result<()> {
|
||||
anyhow::bail!("Import feature not enabled. Compile with --features import")
|
||||
}
|
||||
@@ -14,6 +14,8 @@
|
||||
mod completion;
|
||||
mod config;
|
||||
mod doctor;
|
||||
#[cfg(feature = "import")]
|
||||
pub mod import;
|
||||
mod mcp;
|
||||
pub mod memory;
|
||||
pub mod oauth_defaults;
|
||||
@@ -26,6 +28,8 @@ mod tool;
|
||||
pub use completion::Completion;
|
||||
pub use config::{ConfigCommand, run_config_command};
|
||||
pub use doctor::run_doctor_command;
|
||||
#[cfg(feature = "import")]
|
||||
pub use import::{ImportCommand, run_import_command};
|
||||
pub use mcp::{McpCommand, run_mcp_command};
|
||||
pub use memory::MemoryCommand;
|
||||
pub use memory::run_memory_command_with_db;
|
||||
@@ -183,6 +187,15 @@ pub enum Command {
|
||||
)]
|
||||
Completion(Completion),
|
||||
|
||||
/// Import data from other AI systems
|
||||
#[cfg(feature = "import")]
|
||||
#[command(
|
||||
subcommand,
|
||||
about = "Import from other AI systems",
|
||||
long_about = "Migrate data from other AI assistants like OpenClaw.\nExample: ironclaw import openclaw"
|
||||
)]
|
||||
Import(ImportCommand),
|
||||
|
||||
/// Run as a sandboxed worker inside a Docker container (internal use).
|
||||
/// This is invoked automatically by the orchestrator, not by users directly.
|
||||
#[command(hide = true)]
|
||||
@@ -282,6 +295,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "import")]
|
||||
fn test_help_output() {
|
||||
let mut cmd = Cli::command();
|
||||
let help = cmd.render_help().to_string();
|
||||
@@ -289,9 +303,26 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "import"))]
|
||||
fn test_help_output_without_import() {
|
||||
let mut cmd = Cli::command();
|
||||
let help = cmd.render_help().to_string();
|
||||
assert_snapshot!(help);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "import")]
|
||||
fn test_long_help_output() {
|
||||
let mut cmd = Cli::command();
|
||||
let help = cmd.render_long_help().to_string();
|
||||
assert_snapshot!(help);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "import"))]
|
||||
fn test_long_help_output_without_import() {
|
||||
let mut cmd = Cli::command();
|
||||
let help = cmd.render_long_help().to_string();
|
||||
assert_snapshot!(help);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: src/cli/mod.rs
|
||||
assertion_line: 302
|
||||
expression: help
|
||||
---
|
||||
Secure personal AI assistant that protects your data and expands its capabilities
|
||||
@@ -19,6 +20,7 @@ Commands:
|
||||
doctor Run diagnostics
|
||||
status Show system status
|
||||
completion Generate completions
|
||||
import Import from other AI systems
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: src/cli/mod.rs
|
||||
assertion_line: 310
|
||||
expression: help
|
||||
---
|
||||
Secure personal AI assistant that protects your data and expands its capabilities
|
||||
|
||||
Usage: ironclaw [OPTIONS] [COMMAND]
|
||||
|
||||
Commands:
|
||||
run Run the AI agent
|
||||
onboard Run interactive setup wizard
|
||||
config Manage app configs
|
||||
tool Manage WASM tools
|
||||
registry Browse/install extensions
|
||||
mcp Manage MCP servers
|
||||
memory Manage workspace memory
|
||||
pairing Manage DM pairing
|
||||
service Manage OS service
|
||||
doctor Run diagnostics
|
||||
status Show system status
|
||||
completion Generate completions
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
--cli-only Run in interactive CLI mode only (disable other channels)
|
||||
--no-db Skip database connection (for testing)
|
||||
-m, --message <MESSAGE> Single message mode - send one message and exit
|
||||
-c, --config <CONFIG> Configuration file path (optional, uses env vars by default)
|
||||
--no-onboard Skip first-run onboarding check
|
||||
-h, --help Print help (see more with '--help')
|
||||
-V, --version Print version
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: src/cli/mod.rs
|
||||
assertion_line: 318
|
||||
expression: help
|
||||
---
|
||||
IronClaw is a secure AI assistant. Use 'ironclaw <subcommand> --help' for details.
|
||||
@@ -22,6 +23,7 @@ Commands:
|
||||
doctor Run diagnostics
|
||||
status Show system status
|
||||
completion Generate completions
|
||||
import Import from other AI systems
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
source: src/cli/mod.rs
|
||||
assertion_line: 326
|
||||
expression: help
|
||||
---
|
||||
IronClaw is a secure AI assistant. Use 'ironclaw <subcommand> --help' for details.
|
||||
Examples:
|
||||
ironclaw run # Start the agent
|
||||
ironclaw config list # List configs
|
||||
|
||||
Usage: ironclaw [OPTIONS] [COMMAND]
|
||||
|
||||
Commands:
|
||||
run Run the AI agent
|
||||
onboard Run interactive setup wizard
|
||||
config Manage app configs
|
||||
tool Manage WASM tools
|
||||
registry Browse/install extensions
|
||||
mcp Manage MCP servers
|
||||
memory Manage workspace memory
|
||||
pairing Manage DM pairing
|
||||
service Manage OS service
|
||||
doctor Run diagnostics
|
||||
status Show system status
|
||||
completion Generate completions
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
--cli-only
|
||||
Run in interactive CLI mode only (disable other channels)
|
||||
|
||||
--no-db
|
||||
Skip database connection (for testing)
|
||||
|
||||
-m, --message <MESSAGE>
|
||||
Single message mode - send one message and exit
|
||||
|
||||
-c, --config <CONFIG>
|
||||
Configuration file path (optional, uses env vars by default)
|
||||
|
||||
--no-onboard
|
||||
Skip first-run onboarding check
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
-V, --version
|
||||
Print version
|
||||
Reference in New Issue
Block a user