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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ members = [
"crates/astrid-types",
"crates/astrid-hooks",
"crates/astrid-integration-tests",
"crates/astrid-ipc-client",
"crates/astrid-kernel",
"crates/astrid-mcp",
"crates/astrid-mcp-bridge",
"crates/astrid-openclaw",
"crates/astrid-prelude",
"crates/astrid-storage",
Expand Down Expand Up @@ -46,8 +48,10 @@ astrid-core = { path = "crates/astrid-core", version = "0.6.0" }
astrid-crypto = { path = "crates/astrid-crypto", version = "0.6.0" }
astrid-events = { path = "crates/astrid-events", version = "0.6.0" }
astrid-hooks = { path = "crates/astrid-hooks", version = "0.6.0" }
astrid-ipc-client = { path = "crates/astrid-ipc-client", version = "0.6.0" }
astrid-kernel = { path = "crates/astrid-kernel", version = "0.6.0" }
astrid-mcp = { path = "crates/astrid-mcp", version = "0.6.0" }
astrid-mcp-bridge = { path = "crates/astrid-mcp-bridge", version = "0.6.0" }
astrid-openclaw = { path = "crates/astrid-openclaw", version = "0.6.0" }
astrid-prelude = { path = "crates/astrid-prelude", version = "0.6.0" }
astrid-storage = { path = "crates/astrid-storage", version = "0.6.0" }
Expand Down Expand Up @@ -102,8 +106,9 @@ rand = "0.8"
ratatui = { version = "0.29", features = ["unstable-rendered-line-info"] }
regex = "1.10"
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json", "stream"] }
rmcp = { version = "0.15", features = ["client", "transport-child-process", "transport-io", "elicitation"] }
rmcp = { version = "0.15", features = ["client", "server", "macros", "transport-child-process", "transport-io", "elicitation"] }
rustyline = { version = "15", features = ["derive"] }
schemars = "0.8"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The schemars dependency has been added to the workspace. If this component is intended to be self-contained and eventually moved to its own repository, please pin the dependency inline in the crate's Cargo.toml rather than using workspace dependencies to avoid creating tight coupling (Rule: Pin dependencies inline). Otherwise, please justify this addition and verify that it does not duplicate existing functionality already present in the workspace.

References
  1. Flag any new dependencies added to Cargo.toml. Demand that the PR author justifies the addition and verifies it doesn't duplicate existing functionality in the workspace. (link)
  2. For components intended to be self-contained and eventually moved to their own repository, pin dependencies inline in their Cargo.toml rather than using workspace dependencies to avoid creating tight coupling.

semver = "1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
2 changes: 2 additions & 0 deletions crates/astrid-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ astrid-config = { workspace = true }
astrid-core = { workspace = true }
blake3 = { workspace = true }
astrid-events = { workspace = true }
astrid-ipc-client = { workspace = true }
astrid-mcp-bridge = { workspace = true }
astrid-types = { workspace = true }
astrid-daemon = { workspace = true }
astrid-storage = { workspace = true, features = ["keychain"] }
Expand Down
2 changes: 1 addition & 1 deletion crates/astrid-cli/src/admin_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use astrid_types::kernel::{
use serde_json::Value;
use uuid::Uuid;

use crate::socket_client::SocketClient;
use astrid_ipc_client::socket_client::SocketClient;

/// Topic prefix for admin requests sent by the CLI.
const ADMIN_INPUT_PREFIX: &str = "astrid.v1.admin.";
Expand Down
2 changes: 1 addition & 1 deletion crates/astrid-cli/src/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use anyhow::{Context, Result};
use crate::cli::Cli;
use crate::commands;
use crate::formatter::OutputFormat;
use crate::socket_client;
use astrid_ipc_client::socket_client;
use crate::theme;

/// Ensure `~/.astrid/` exists and run first-boot init if needed.
Expand Down
9 changes: 8 additions & 1 deletion crates/astrid-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use crate::commands::{
agent::AgentCommand, audit::AuditArgs, budget::BudgetCommand, caps::CapsCommand,
capsule::config::ConfigArgs as CapsuleConfigArgs, capsule::show::ShowArgs as CapsuleShowArgs,
completions::CompletionsArgs, doctor::DoctorArgs, gc::GcArgs, group::GroupCommand,
logs::LogsArgs, ps::PsArgs, quota::QuotaCommand, run::RunArgs, secret::SecretCommand,
logs::LogsArgs, mcp::McpCommand, ps::PsArgs, quota::QuotaCommand, run::RunArgs,
secret::SecretCommand,
setup::SetupArgs, top::TopArgs, trust::TrustCommand, version::VersionArgs,
voucher::VoucherCommand, who::WhoArgs,
};
Expand Down Expand Up @@ -179,6 +180,12 @@ pub(crate) enum Commands {
distro: String,
},

/// Bridge Astrid tools to external MCP clients (Claude Code, etc.)
Mcp {
#[command(subcommand)]
command: McpCommand,
},

/// View resolved configuration, edit it in `$EDITOR`, or print paths.
Config {
#[command(subcommand)]
Expand Down
5 changes: 3 additions & 2 deletions crates/astrid-cli/src/commands/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use colored::Colorize;

use crate::formatter::{OutputFormat, OutputFormatter, create_formatter};
use crate::repl::ReadlineEvent;
use crate::socket_client::SocketClient;
use astrid_ipc_client::socket_client::SocketClient;
use crate::theme::Theme;

/// Reason sent (and displayed) when the JSON REPL auto-denies an approval request.
Expand Down Expand Up @@ -85,7 +85,8 @@ async fn run_json_chat(
continue;
}

client.send_input(input.to_string()).await?;
let caller = crate::context::active_agent()?.to_string();
client.send_input(input.to_string(), caller).await?;

if !drain_agent_response(client, session_id, &mut *formatter).await? {
return Ok(());
Expand Down
5 changes: 3 additions & 2 deletions crates/astrid-cli/src/commands/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
use anyhow::{Context, Result};

use crate::bootstrap::find_companion_binary;
use crate::{socket_client, theme};
use crate::theme;
use astrid_ipc_client::socket_client;

/// Build a hint string pointing the user to the daemon log directory.
fn log_hint() -> String {
Expand Down Expand Up @@ -203,7 +204,7 @@ pub(crate) async fn handle_status() -> Result<()> {
)
.await?;
if let Some(astrid_types::kernel::KernelResponse::Status(status)) =
crate::socket_client::SocketClient::extract_kernel_response(&raw)
astrid_ipc_client::socket_client::SocketClient::extract_kernel_response(&raw)
{
let uptime_display = format_uptime(status.uptime_secs);
println!(
Expand Down
2 changes: 1 addition & 1 deletion crates/astrid-cli/src/commands/doctor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use clap::Args;
use colored::Colorize;
use uuid::Uuid;

use crate::socket_client::SocketClient;
use astrid_ipc_client::socket_client::SocketClient;
use crate::theme::Theme;

#[derive(Args, Debug, Clone)]
Expand Down
6 changes: 4 additions & 2 deletions crates/astrid-cli/src/commands/headless.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use std::io::IsTerminal;
use anyhow::{Context, Result};

use super::daemon;
use crate::{formatter, socket_client, tui};
use crate::{formatter, tui};
use astrid_ipc_client::socket_client;

/// Resolve the `--session` flag value into a [`uuid::Uuid`].
///
Expand Down Expand Up @@ -130,7 +131,8 @@ pub(crate) async fn run_headless(
};

// Send the prompt and collect the streaming response
client.send_input(full_prompt).await?;
let caller = crate::context::active_agent()?.to_string();
client.send_input(full_prompt, caller).await?;
let (response_text, tool_calls) =
collect_response(&mut client, &session_id, format, auto_approve).await?;

Expand Down
26 changes: 26 additions & 0 deletions crates/astrid-cli/src/commands/mcp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//! `astrid mcp` subcommand group — bridges Astrid capsule tools to
//! external MCP clients (e.g. Claude Code).

use std::process::ExitCode;

use anyhow::Result;
use clap::Subcommand;

use astrid_mcp_bridge::{BridgeConfig, run_stdio};

#[derive(Subcommand)]
pub(crate) enum McpCommand {
/// Run the MCP bridge over stdio. Intended to be spawned by an
/// MCP client (e.g. Claude Code via .mcp.json) — not invoked
/// interactively.
Bridge,
}

pub(crate) async fn run(command: McpCommand) -> Result<ExitCode> {
match command {
McpCommand::Bridge => {
run_stdio(BridgeConfig::default()).await?;
Ok(ExitCode::SUCCESS)
},
}
}
1 change: 1 addition & 0 deletions crates/astrid-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub(crate) mod group;
pub(crate) mod headless;
pub(crate) mod init;
pub(crate) mod logs;
pub(crate) mod mcp;
pub(crate) mod ps;
pub(crate) mod quota;
pub(crate) mod restart;
Expand Down
6 changes: 3 additions & 3 deletions crates/astrid-cli/src/commands/ps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use colored::Colorize;
use serde::Serialize;
use uuid::Uuid;

use crate::socket_client::SocketClient;
use astrid_ipc_client::socket_client::SocketClient;
use crate::theme::Theme;
use crate::value_formatter::{ValueFormat, emit_structured};

Expand All @@ -38,7 +38,7 @@ pub(crate) struct CapsuleRow {
/// Entry point for `astrid ps`.
pub(crate) async fn run(args: PsArgs) -> Result<ExitCode> {
let format = ValueFormat::parse(&args.format);
let socket_path = crate::socket_client::proxy_socket_path();
let socket_path = astrid_ipc_client::socket_client::proxy_socket_path();
if !socket_path.exists() {
if format.is_pretty() {
println!("{}", Theme::info("No Astrid daemon is running."));
Expand Down Expand Up @@ -66,7 +66,7 @@ pub(crate) async fn run(args: PsArgs) -> Result<ExitCode> {
std::time::Duration::from_secs(10),
)
.await?;
let entries = match crate::socket_client::SocketClient::extract_kernel_response(&raw) {
let entries = match astrid_ipc_client::socket_client::SocketClient::extract_kernel_response(&raw) {
Some(astrid_types::kernel::KernelResponse::CapsuleMetadata(list)) => list,
_ => Vec::new(),
};
Expand Down
2 changes: 1 addition & 1 deletion crates/astrid-cli/src/commands/restart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use anyhow::Result;
use tokio::time::sleep;

use crate::commands::daemon;
use crate::socket_client;
use astrid_ipc_client::socket_client;
use crate::theme::Theme;

/// Entry point for `astrid restart`.
Expand Down
6 changes: 3 additions & 3 deletions crates/astrid-cli/src/commands/who.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use serde::Serialize;
use uuid::Uuid;

use crate::commands::daemon;
use crate::socket_client::SocketClient;
use astrid_ipc_client::socket_client::SocketClient;
use crate::theme::Theme;
use crate::value_formatter::{ValueFormat, emit_structured};

Expand All @@ -47,7 +47,7 @@ pub(crate) struct Connection {
/// Entry point for `astrid who`.
pub(crate) async fn run(args: WhoArgs) -> Result<ExitCode> {
let format = ValueFormat::parse(&args.format);
let socket_path = crate::socket_client::proxy_socket_path();
let socket_path = astrid_ipc_client::socket_client::proxy_socket_path();
if !socket_path.exists() {
if format.is_pretty() {
println!("{}", Theme::info("No Astrid daemon is running."));
Expand Down Expand Up @@ -78,7 +78,7 @@ pub(crate) async fn run(args: WhoArgs) -> Result<ExitCode> {
// Reuse the shared envelope extractor so this command tracks any
// future change to the IPC response wrapper without re-implementing
// the `{type, value}` unwrap inline (matches `ps` / `daemon` usage).
let status = match crate::socket_client::SocketClient::extract_kernel_response(&raw) {
let status = match astrid_ipc_client::socket_client::SocketClient::extract_kernel_response(&raw) {
Some(astrid_types::kernel::KernelResponse::Status(s)) => Some(s),
_ => None,
};
Expand Down
1 change: 1 addition & 0 deletions crates/astrid-cli/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ async fn dispatch_subcommand(
commands::self_update::ensure_path_setup()?;
Ok(ExitCode::SUCCESS)
},
Some(Commands::Mcp { command }) => commands::mcp::run(command).await,
Some(Commands::Capsule { command }) => dispatch_capsule(command).await,
Some(Commands::Distro { command }) => dispatch_distro(command).await,
Some(Commands::Wit { command }) => dispatch_wit(&command),
Expand Down
2 changes: 0 additions & 2 deletions crates/astrid-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ mod context;
mod dispatch;
mod formatter;
mod repl;
/// The socket client for interacting with the Kernel.
pub mod socket_client;
mod theme;
mod tui;
mod value_formatter;
Expand Down
7 changes: 5 additions & 2 deletions crates/astrid-cli/src/tui/headless.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use ratatui::style::{Color, Modifier};

use super::render;
use super::state::{App, MessageRole, UiState};
use crate::socket_client::SocketClient;
use astrid_ipc_client::socket_client::SocketClient;

/// Convert a ratatui `Color` to an ANSI SGR foreground code.
fn fg_ansi(color: Color) -> Option<String> {
Expand Down Expand Up @@ -209,7 +209,10 @@ pub(crate) async fn run(cfg: HeadlessConfig<'_>) -> anyhow::Result<()> {
start_time: Instant::now(),
dots: 0,
};
cfg.client.send_input(cfg.prompt.to_string()).await?;
let caller = crate::context::active_agent()?.to_string();
cfg.client
.send_input(cfg.prompt.to_string(), caller)
.await?;
snapshot(&mut terminal, &mut app, "input_sent");

let timeout = Duration::from_secs(120);
Expand Down
24 changes: 18 additions & 6 deletions crates/astrid-cli/src/tui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use crossterm::{
};
use ratatui::{Terminal, backend::CrosstermBackend};

use crate::socket_client::SocketClient;
use astrid_ipc_client::socket_client::SocketClient;
use state::{App, MessageRole, PendingAction, UiState};

/// Type alias for our terminal.
Expand Down Expand Up @@ -568,11 +568,23 @@ async fn handle_pending_actions(
};

// Send to daemon.
if let Err(e) = client.send_input(content).await {
app.push_notice(&format!("Failed to send input: {e}"));
app.state = UiState::Error {
message: format!("Send failed: {e}"),
};
match crate::context::active_agent() {
Ok(caller) => {
if let Err(e) =
client.send_input(content, caller.to_string()).await
{
app.push_notice(&format!("Failed to send input: {e}"));
app.state = UiState::Error {
message: format!("Send failed: {e}"),
};
}
},
Err(e) => {
app.push_notice(&format!("Failed to resolve active agent: {e}"));
app.state = UiState::Error {
message: format!("Active agent resolution failed: {e}"),
};
},
}
}
},
Expand Down
20 changes: 20 additions & 0 deletions crates/astrid-ipc-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "astrid-ipc-client"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
description = "Astrid daemon socket client — handshake + length-prefixed JSON IpcMessage I/O."

[dependencies]
anyhow = { workspace = true }
astrid-core = { workspace = true }
astrid-types = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["net", "io-util"] }
tracing = { workspace = true }

[lints]
workspace = true
8 changes: 8 additions & 0 deletions crates/astrid-ipc-client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! Astrid daemon socket client.
//!
//! Length-prefixed JSON `IpcMessage` I/O over the system Unix
//! socket with bearer-token handshake. Extracted from `astrid-cli`
//! for reuse by the MCP bridge.

pub mod socket_client;
pub use socket_client::SocketClient;
Loading