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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions crates/mofa-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ path = "src/main.rs"
# Core
mofa-sdk = { path = "../mofa-sdk", version = "0.1" }
mofa-kernel = { path = "../mofa-kernel", version = "0.1", features = ["config"] }
mofa-runtime = { path = "../mofa-runtime", version = "0.1" }
mofa-foundation = { path = "../mofa-foundation", version = "0.1" }
config.workspace = true
tokio = { workspace = true }
anyhow = { workspace = true }
async-trait = { workspace = true }
chrono = { workspace = true }

# CLI
clap = { version = "4", features = ["derive", "env"] }
Expand Down
66 changes: 29 additions & 37 deletions crates/mofa-cli/src/commands/agent/list.rs
Original file line number Diff line number Diff line change
@@ -1,56 +1,52 @@
//! `mofa agent list` command implementation

use crate::context::CliContext;
use crate::output::Table;
use colored::Colorize;
use serde::Serialize;

/// Execute the `mofa agent list` command
pub fn run(running_only: bool, show_all: bool) -> anyhow::Result<()> {
pub async fn run(ctx: &CliContext, running_only: bool, _show_all: bool) -> anyhow::Result<()> {
println!("{} Listing agents", "→".green());

if running_only {
println!(" Showing running agents only");
} else if show_all {
println!(" Showing all agents");
}

println!();

// TODO: Implement actual agent listing from state store
// For now, show example output
let agents_metadata = ctx.agent_registry.list().await;

if agents_metadata.is_empty() {
println!(" No agents registered.");
println!();
println!(
" Use {} to start an agent.",
"mofa agent start <agent_id>".cyan()
);
return Ok(());
}

let agents = vec![
AgentInfo {
id: "agent-001".to_string(),
name: "MyAgent".to_string(),
status: "running".to_string(),
uptime: Some("5m 32s".to_string()),
provider: Some("openai".to_string()),
model: Some("gpt-4o".to_string()),
},
AgentInfo {
id: "agent-002".to_string(),
name: "TestAgent".to_string(),
status: "stopped".to_string(),
uptime: None,
provider: None,
model: None,
},
];
let agents: Vec<AgentInfo> = agents_metadata
.iter()
.map(|m| {
let status = format!("{:?}", m.state);
AgentInfo {
id: m.id.clone(),
name: m.name.clone(),
status,
description: m.description.clone(),
}
})
.collect();

// Filter based on flags
let filtered: Vec<_> = if running_only {
agents
.iter()
.filter(|a| a.status == "running")
.cloned()
.into_iter()
.filter(|a| a.status == "Running" || a.status == "Ready")
.collect()
} else {
agents
};

if filtered.is_empty() {
println!(" No agents found.");
println!(" No agents found matching criteria.");
return Ok(());
}

Expand All @@ -70,9 +66,5 @@ struct AgentInfo {
name: String,
status: String,
#[serde(skip_serializing_if = "Option::is_none")]
uptime: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
provider: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
model: Option<String>,
description: Option<String>,
}
37 changes: 32 additions & 5 deletions crates/mofa-cli/src/commands/agent/restart.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,42 @@
//! `mofa agent restart` command implementation

use crate::context::CliContext;
use colored::Colorize;

/// Execute the `mofa agent restart` command
pub fn run(agent_id: &str, _config: Option<&std::path::Path>) -> anyhow::Result<()> {
pub async fn run(
ctx: &CliContext,
agent_id: &str,
config: Option<&std::path::Path>,
) -> anyhow::Result<()> {
println!("{} Restarting agent: {}", "→".green(), agent_id.cyan());

// TODO: Implement actual agent restart logic
// This would involve:
// 1. Stopping the agent
// 2. Starting it again with the same config
// Stop the agent if it's running
if ctx.agent_registry.contains(agent_id).await {
// Attempt graceful shutdown
if let Some(agent) = ctx.agent_registry.get(agent_id).await {
let mut agent_guard = agent.write().await;
if let Err(e) = agent_guard.shutdown().await {
println!(
" {} Graceful shutdown failed: {}",
"!".yellow(),
e
);
}
}

ctx.agent_registry
.unregister(agent_id)
.await
.map_err(|e| anyhow::anyhow!("Failed to unregister agent: {}", e))?;

println!(" Agent stopped");
} else {
println!(" Agent was not running");
}

// Start it again
super::start::run(ctx, agent_id, config, false).await?;

println!("{} Agent '{}' restarted", "✓".green(), agent_id);

Expand Down
79 changes: 73 additions & 6 deletions crates/mofa-cli/src/commands/agent/start.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,87 @@
//! `mofa agent start` command implementation

use crate::config::loader::ConfigLoader;
use crate::context::CliContext;
use colored::Colorize;

/// Execute the `mofa agent start` command
pub fn run(agent_id: &str, _config: Option<&std::path::Path>, daemon: bool) -> anyhow::Result<()> {
pub async fn run(
ctx: &CliContext,
agent_id: &str,
config_path: Option<&std::path::Path>,
daemon: bool,
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The daemon parameter is accepted but not implemented in the function body. It is only used for printing a message on line 17, but the actual daemon mode functionality (backgrounding the process, proper daemon setup, etc.) is not implemented. Consider either implementing daemon mode or removing this parameter until the feature is ready.

Copilot uses AI. Check for mistakes.
) -> anyhow::Result<()> {
println!("{} Starting agent: {}", "→".green(), agent_id.cyan());

if daemon {
println!(" Mode: {}", "daemon".yellow());
}

// TODO: Implement actual agent starting logic
// This would involve:
// 1. Loading agent configuration
// 2. Starting the agent process
// 3. Storing agent state/PID
// Check if agent is already registered
if ctx.agent_registry.contains(agent_id).await {
anyhow::bail!("Agent '{}' is already registered", agent_id);
}

// Load agent configuration
let agent_config = if let Some(path) = config_path {
println!(" Config: {}", path.display().to_string().cyan());
let loader = ConfigLoader::new();
let cli_config = loader.load(path)?;
println!(" Agent: {}", cli_config.agent.name.white());

// Convert CLI AgentConfig to kernel AgentConfig
mofa_kernel::agent::config::AgentConfig::new(agent_id, &cli_config.agent.name)
} else {
// Try to auto-discover configuration
let loader = ConfigLoader::new();
match loader.find_config() {
Some(found_path) => {
println!(
" Config: {} (auto-discovered)",
found_path.display().to_string().cyan()
);
let cli_config = loader.load(&found_path)?;
println!(" Agent: {}", cli_config.agent.name.white());
mofa_kernel::agent::config::AgentConfig::new(agent_id, &cli_config.agent.name)
}
None => {
println!(
" {} No config file found, using defaults",
"!".yellow()
);
mofa_kernel::agent::config::AgentConfig::new(agent_id, agent_id)
}
}
};

// Check if a matching factory type is available
let factory_types = ctx.agent_registry.list_factory_types().await;
if factory_types.is_empty() {
println!(
" {} No agent factories registered. Agent registered with config only.",
"!".yellow()
);
println!(" Agent config stored for: {}", agent_config.name.cyan());
} else {
// Try to create via factory
let type_id = factory_types.first().unwrap();
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using unwrap() here is technically safe since factory_types.is_empty() was already checked in the if condition, but it's not idiomatic. Consider using pattern matching or a more explicit approach to avoid unwrap in production code. For example, you could use let type_id = &factory_types[0]; or restructure to avoid the unwrap entirely.

Suggested change
let type_id = factory_types.first().unwrap();
let type_id = &factory_types[0];

Copilot uses AI. Check for mistakes.
match ctx
.agent_registry
.create_and_register(type_id, agent_config.clone())
.await
{
Ok(_) => {
println!("{} Agent '{}' created and registered", "✓".green(), agent_id);
}
Err(e) => {
println!(
" {} Failed to create agent via factory: {}",
"!".yellow(),
e
);
}
}
}

println!("{} Agent '{}' started", "✓".green(), agent_id);

Expand Down
54 changes: 48 additions & 6 deletions crates/mofa-cli/src/commands/agent/status.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,63 @@
//! `mofa agent status` command implementation

use crate::context::CliContext;
use colored::Colorize;

/// Execute the `mofa agent status` command
pub fn run(agent_id: Option<&str>) -> anyhow::Result<()> {
pub async fn run(ctx: &CliContext, agent_id: Option<&str>) -> anyhow::Result<()> {
if let Some(id) = agent_id {
// Show status for a specific agent
println!("{} Agent status: {}", "→".green(), id.cyan());
println!();
println!(" ID: {}", id);
println!(" Status: {}", "Running".green());
println!(" Uptime: {}", "5m 32s".white());

match ctx.agent_registry.get_metadata(id).await {
Some(metadata) => {
println!(" ID: {}", metadata.id.cyan());
println!(" Name: {}", metadata.name.white());
println!(" State: {}", format!("{:?}", metadata.state).green());
if let Some(desc) = &metadata.description {
println!(" Description: {}", desc.white());
}
if let Some(ver) = &metadata.version {
println!(" Version: {}", ver.white());
}
let caps = &metadata.capabilities;
if !caps.tags.is_empty() {
let tags: Vec<_> = caps.tags.iter().cloned().collect();
println!(" Tags: {}", tags.join(", ").white());
}
}
None => {
println!(" Agent '{}' not found in registry", id);
println!();
println!(
" Use {} to see available agents.",
"mofa agent list".cyan()
);
}
}
} else {
// Show summary of all agents
println!("{} Agent Status", "→".green());
println!("{} Agent Status Summary", "→".green());
println!();
println!(" No agents currently running.");

let stats = ctx.agent_registry.stats().await;

if stats.total_agents == 0 {
println!(" No agents currently registered.");
return Ok(());
}

println!(" Total agents: {}", stats.total_agents);
if !stats.by_state.is_empty() {
println!(" By state:");
for (state, count) in &stats.by_state {
println!(" {}: {}", state, count);
}
}
if stats.factory_count > 0 {
println!(" Factories: {}", stats.factory_count);
}
}

Ok(())
Expand Down
41 changes: 34 additions & 7 deletions crates/mofa-cli/src/commands/agent/stop.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,45 @@
//! `mofa agent stop` command implementation

use crate::context::CliContext;
use colored::Colorize;

/// Execute the `mofa agent stop` command
pub fn run(agent_id: &str) -> anyhow::Result<()> {
pub async fn run(ctx: &CliContext, agent_id: &str) -> anyhow::Result<()> {
println!("{} Stopping agent: {}", "→".green(), agent_id.cyan());

// TODO: Implement actual agent stopping logic
// This would involve:
// 1. Looking up the agent's PID/state
// 2. Sending a shutdown signal
// 3. Waiting for graceful shutdown
// Check if agent exists
if !ctx.agent_registry.contains(agent_id).await {
anyhow::bail!("Agent '{}' not found in registry", agent_id);
}

println!("{} Agent '{}' stopped", "✓".green(), agent_id);
// Attempt graceful shutdown via the agent instance
if let Some(agent) = ctx.agent_registry.get(agent_id).await {
let mut agent_guard = agent.write().await;
if let Err(e) = agent_guard.shutdown().await {
println!(
" {} Graceful shutdown failed: {}",
"!".yellow(),
e
);
}
}

// Unregister from the registry
let removed = ctx
.agent_registry
.unregister(agent_id)
.await
.map_err(|e| anyhow::anyhow!("Failed to unregister agent: {}", e))?;

if removed {
println!("{} Agent '{}' stopped and unregistered", "✓".green(), agent_id);
} else {
println!(
"{} Agent '{}' was not in the registry",
"!".yellow(),
agent_id
);
}

Ok(())
}
Loading
Loading