feat: add main features

Agents for developpement added + frontend add + backend added. Git viewer created + agent and template creator + layout and project creator
This commit is contained in:
2026-06-06 01:27:01 +02:00
parent 55b3bee2c8
commit 307ae71857
273 changed files with 48740 additions and 0 deletions

File diff suppressed because it is too large Load Diff

1332
crates/app-tauri/src/dto.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,186 @@
//! `TauriEventRelay` — bridges the domain [`EventBus`] to Tauri events
//! (backend → frontend push channel, ARCHITECTURE §2 "Events").
//!
//! The relay subscribes to the bus and re-emits each [`DomainEvent`] as a Tauri
//! event named [`DOMAIN_EVENT`], carrying a serialisable [`DomainEventDto`]
//! payload (the domain event itself is deliberately not `Serialize`; the wire
//! format is owned here, the infrastructure/presentation layer).
//!
//! High-frequency `PtyOutput` is intentionally *not* relayed through this global
//! event; it goes through per-session [`crate::pty::PtyBridge`] channels instead.
use serde::Serialize;
use tauri::{AppHandle, Emitter};
use domain::events::DomainEvent;
use infrastructure::TokioBroadcastEventBus;
/// Name of the Tauri event carrying relayed [`DomainEvent`]s.
pub const DOMAIN_EVENT: &str = "domain://event";
/// Serialisable mirror of [`DomainEvent`] for the IPC wire (camelCase, tagged).
///
/// `type` is the discriminant; payload fields are flattened per variant. This is
/// the single owner of the event wire format on the backend side.
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum DomainEventDto {
/// A project was created.
#[serde(rename_all = "camelCase")]
ProjectCreated {
/// Project id (UUID string).
project_id: String,
},
/// An agent was launched.
#[serde(rename_all = "camelCase")]
AgentLaunched {
/// Agent id.
agent_id: String,
/// Session id.
session_id: String,
},
/// An agent exited.
#[serde(rename_all = "camelCase")]
AgentExited {
/// Agent id.
agent_id: String,
/// Exit code.
code: i32,
},
/// A template was updated.
#[serde(rename_all = "camelCase")]
TemplateUpdated {
/// Template id.
template_id: String,
/// New version.
version: u64,
},
/// A synchronized agent drifted from its template.
#[serde(rename_all = "camelCase")]
AgentDriftDetected {
/// Agent id.
agent_id: String,
/// Current version.
from: u64,
/// Available version.
to: u64,
},
/// A synchronized agent was brought up to date.
#[serde(rename_all = "camelCase")]
AgentSynced {
/// Agent id.
agent_id: String,
/// Version synced to.
to: u64,
},
/// A tab's layout changed.
#[serde(rename_all = "camelCase")]
LayoutChanged {
/// Project id.
project_id: String,
},
/// A remote connection was established.
#[serde(rename_all = "camelCase")]
RemoteConnected {
/// Project id.
project_id: String,
},
/// Git state changed.
#[serde(rename_all = "camelCase")]
GitStateChanged {
/// Project id.
project_id: String,
},
/// Raw PTY output (normally routed to a per-session channel, not here).
#[serde(rename_all = "camelCase")]
PtyOutput {
/// Session id.
session_id: String,
/// Output bytes.
bytes: Vec<u8>,
},
}
impl From<&DomainEvent> for DomainEventDto {
fn from(e: &DomainEvent) -> Self {
match e {
DomainEvent::ProjectCreated { project_id } => Self::ProjectCreated {
project_id: project_id.to_string(),
},
DomainEvent::AgentLaunched {
agent_id,
session_id,
} => Self::AgentLaunched {
agent_id: agent_id.to_string(),
session_id: session_id.to_string(),
},
DomainEvent::AgentExited { agent_id, code } => Self::AgentExited {
agent_id: agent_id.to_string(),
code: *code,
},
DomainEvent::TemplateUpdated {
template_id,
version,
} => Self::TemplateUpdated {
template_id: template_id.to_string(),
version: version.get(),
},
DomainEvent::AgentDriftDetected {
agent_id,
from,
to,
} => Self::AgentDriftDetected {
agent_id: agent_id.to_string(),
from: from.get(),
to: to.get(),
},
DomainEvent::AgentSynced { agent_id, to } => Self::AgentSynced {
agent_id: agent_id.to_string(),
to: to.get(),
},
DomainEvent::LayoutChanged { project_id } => Self::LayoutChanged {
project_id: project_id.to_string(),
},
DomainEvent::RemoteConnected { project_id } => Self::RemoteConnected {
project_id: project_id.to_string(),
},
DomainEvent::GitStateChanged { project_id } => Self::GitStateChanged {
project_id: project_id.to_string(),
},
DomainEvent::PtyOutput { session_id, bytes } => Self::PtyOutput {
session_id: session_id.to_string(),
bytes: bytes.clone(),
},
}
}
}
/// Subscribes the relay to the bus and spawns a background task that forwards
/// every [`DomainEvent`] to the frontend as a [`DOMAIN_EVENT`] Tauri event.
///
/// Uses the bus's raw async broadcast receiver so the relay runs cooperatively
/// on the Tokio runtime (no blocking thread). Returns immediately; the spawned
/// task lives for the duration of the app.
pub fn spawn_relay(app: AppHandle, bus: &TokioBroadcastEventBus) {
use tokio::sync::broadcast::error::RecvError;
let mut rx = bus.raw_receiver();
tauri::async_runtime::spawn(async move {
loop {
match rx.recv().await {
Ok(event) => {
// Skip high-frequency PTY output on the global channel.
if matches!(event, DomainEvent::PtyOutput { .. }) {
continue;
}
let dto = DomainEventDto::from(&event);
let _ = app.emit(DOMAIN_EVENT, dto);
}
// The bus dropped some events for this slow receiver; keep going.
Err(RecvError::Lagged(_)) => continue,
// The bus was dropped (app shutting down); stop the relay.
Err(RecvError::Closed) => break,
}
}
});
}

103
crates/app-tauri/src/lib.rs Normal file
View File

@ -0,0 +1,103 @@
//! # IdeA — `app-tauri` (presentation / driving adapter + composition root)
//!
//! This crate is the **only** place that knows every other crate. It:
//! - builds the concrete adapters and injects them into use cases
//! ([`state::AppState`], the composition root),
//! - exposes `#[tauri::command]` handlers ([`commands`]) mapping DTOs ↔ use cases,
//! - relays domain events to the frontend ([`events::TauriEventRelay`]),
//! - hosts the generic PTY↔Channel bridge ([`pty::PtyBridge`]) for L3.
//!
//! The wiring lives in the library (testable) and `main.rs` is a thin shim.
#![forbid(unsafe_code)]
#![warn(missing_docs)]
pub mod commands;
pub mod dto;
pub mod events;
pub mod pty;
pub mod state;
use tauri::Manager;
use state::AppState;
/// Builds and runs the Tauri application.
///
/// Sets up the composition root (resolving the app-data directory via the Tauri
/// path API), registers commands, spawns the event relay, and starts the main
/// window.
///
/// # Panics
/// Panics if the Tauri application fails to build or run (no window/webview), or
/// if the app-data directory cannot be resolved.
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.setup(|app| {
// Resolve the machine-local IDE data directory (ARCHITECTURE §9.2)
// and build the composition root once the app handle exists, so the
// stores receive a concrete path without ever touching Tauri.
let app_data_dir = app
.path()
.app_data_dir()
.expect("failed to resolve the app data directory");
let app_state = AppState::build(app_data_dir);
// Wire the domain event bus → Tauri events relay.
events::spawn_relay(app.handle().clone(), &app_state.event_bus);
app.manage(app_state);
Ok(())
})
.invoke_handler(tauri::generate_handler![
commands::health,
commands::create_project,
commands::open_project,
commands::close_project,
commands::list_projects,
commands::open_terminal,
commands::write_terminal,
commands::resize_terminal,
commands::close_terminal,
commands::load_layout,
commands::mutate_layout,
commands::list_layouts,
commands::create_layout,
commands::rename_layout,
commands::delete_layout,
commands::set_active_layout,
commands::first_run_state,
commands::reference_profiles,
commands::detect_profiles,
commands::list_profiles,
commands::save_profile,
commands::delete_profile,
commands::configure_profiles,
commands::create_agent,
commands::list_agents,
commands::read_agent_context,
commands::update_agent_context,
commands::delete_agent,
commands::launch_agent,
commands::create_template,
commands::update_template,
commands::list_templates,
commands::delete_template,
commands::create_agent_from_template,
commands::detect_agent_drift,
commands::sync_agent_with_template,
commands::git_status,
commands::git_stage,
commands::git_unstage,
commands::git_commit,
commands::git_branches,
commands::git_checkout,
commands::git_log,
commands::git_init,
commands::git_graph,
commands::move_tab_to_new_window,
])
.run(tauri::generate_context!())
.expect("error while running IdeA Tauri application");
}

View File

@ -0,0 +1,16 @@
// Prevents an extra console window on Windows in release builds.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
// WebKitGTK's DMABUF renderer causes a blank/white window on many Linux
// setups (recent Mesa/Nvidia drivers, common on Arch). Disable it before
// the webview initializes, unless the user has explicitly set the variable.
#[cfg(target_os = "linux")]
{
if std::env::var_os("WEBKIT_DISABLE_DMABUF_RENDERER").is_none() {
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
}
}
app_tauri_lib::run();
}

View File

@ -0,0 +1,84 @@
//! Generic **PTY ↔ Tauri Channel** bridge infrastructure.
//!
//! ARCHITECTURE §2 decides that high-frequency PTY byte streams travel over
//! per-session [`tauri::ipc::Channel`]s rather than global events, for
//! throughput and isolation. This module provides the transport-side plumbing
//! that L3 will plug a real `PtyPort` into; here there is **no real PTY** yet —
//! only the registry + the abstraction that routes byte chunks to the right
//! frontend channel.
//!
//! Design:
//! - The frontend opens a terminal and passes a [`tauri::ipc::Channel`] for that
//! session. The backend registers it in [`PtyBridge`] keyed by `SessionId`.
//! - Whatever produces output (the PTY adapter in L3) calls
//! [`PtyBridge::send_output`], which forwards the bytes on the matching
//! channel. Bytes are sent as-is; the frontend xterm wrapper consumes them.
//! - [`PtyBridge::unregister`] tears the channel down on terminal close.
use std::collections::HashMap;
use std::sync::Mutex;
use tauri::ipc::Channel;
use domain::ids::SessionId;
/// A chunk of PTY output bytes destined for a specific session's channel.
///
/// Sent as a raw byte vector; serde encodes it for the IPC channel. Kept as a
/// distinct type so the wire shape can evolve (e.g. add a sequence number for
/// backpressure handling) without touching call sites.
pub type PtyChunk = Vec<u8>;
/// Registry mapping live terminal sessions to their output [`Channel`].
///
/// Thread-safe; cloned `Arc<PtyBridge>` is held in [`crate::state::AppState`].
#[derive(Default)]
pub struct PtyBridge {
channels: Mutex<HashMap<SessionId, Channel<PtyChunk>>>,
}
impl PtyBridge {
/// Creates an empty bridge.
#[must_use]
pub fn new() -> Self {
Self {
channels: Mutex::new(HashMap::new()),
}
}
/// Registers the output channel for a session (called when a terminal is
/// opened, from a `#[tauri::command]` that receives the `Channel` argument).
pub fn register(&self, session: SessionId, channel: Channel<PtyChunk>) {
if let Ok(mut map) = self.channels.lock() {
map.insert(session, channel);
}
}
/// Removes a session's channel (terminal closed).
pub fn unregister(&self, session: &SessionId) {
if let Ok(mut map) = self.channels.lock() {
map.remove(session);
}
}
/// Forwards a chunk of output bytes to a session's channel.
///
/// Returns `true` if the chunk was delivered, `false` if no channel is
/// registered for the session (e.g. already closed). In L3 the PTY adapter's
/// output stream drives this.
pub fn send_output(&self, session: &SessionId, chunk: PtyChunk) -> bool {
let Ok(map) = self.channels.lock() else {
return false;
};
match map.get(session) {
Some(channel) => channel.send(chunk).is_ok(),
None => false,
}
}
/// Number of currently-registered sessions (handy for tests/diagnostics).
#[must_use]
pub fn active_sessions(&self) -> usize {
self.channels.lock().map(|m| m.len()).unwrap_or(0)
}
}

View File

@ -0,0 +1,428 @@
//! Managed application state — the product of the **composition root**.
//!
//! The composition root ([`build_app_state`]) is the *single* place that
//! constructs concrete adapters (`new ConcreteAdapter`) and injects them as
//! `Arc<dyn Port>` into the use cases (ARCHITECTURE §1.1, §10). The use cases
//! are then exposed through `tauri::State<AppState>` to the command handlers.
use std::path::PathBuf;
use std::sync::Arc;
use application::{
CloseProject, CloseTab, CloseTerminal, ConfigureProfiles, CreateAgentFromScratch,
CreateAgentFromTemplate, CreateLayout, CreateProject, CreateTemplate, DeleteAgent,
DeleteLayout, DeleteProfile, DeleteTemplate, DetectAgentDrift, DetectProfiles, FirstRunState,
GitBranches, GitCheckout, GitCommit, GitGraph, GitInit, GitLog, GitStage, GitStatus, GitUnstage,
HealthUseCase, LaunchAgent, ListAgents, ListLayouts, ListProfiles, ListProjects, ListTemplates,
LoadLayout, MoveTabToNewWindow, MutateLayout, OpenProject, OpenTerminal, ReadAgentContext,
ReferenceProfiles, RenameLayout, ResizeTerminal, SaveProfile, SetActiveLayout,
SyncAgentWithTemplate, TerminalSessions, UpdateAgentContext, UpdateTemplate, WriteToTerminal,
};
use domain::ports::{
AgentContextStore, AgentRuntime, Clock, EventBus, FileSystem, GitPort, IdGenerator,
ProcessSpawner, ProfileStore, ProjectStore, PtyPort, TemplateStore,
};
use infrastructure::{
CliAgentRuntime, FsProfileStore, FsProjectStore, FsTemplateStore, Git2Repository,
IdeaiContextStore, LocalFileSystem, LocalProcessSpawner, PortablePtyAdapter, SystemClock,
TokioBroadcastEventBus, UuidGenerator,
};
use crate::pty::PtyBridge;
/// Everything the IPC layer needs at runtime, managed by Tauri.
///
/// Use cases are stored behind `Arc` so handlers clone cheaply. The concrete
/// adapters are owned here and never leak past the composition root as concrete
/// types — downstream code only sees the `Arc<dyn Port>` held inside the use
/// cases.
pub struct AppState {
/// Trivial health use case validating the end-to-end wiring.
pub health: Arc<HealthUseCase>,
/// Create a project (init `.ideai/`, register it).
pub create_project: Arc<CreateProject>,
/// Open a project (load meta + manifest).
pub open_project: Arc<OpenProject>,
/// Close a project (persist state).
pub close_project: Arc<CloseProject>,
/// Close a tab.
pub close_tab: Arc<CloseTab>,
/// List known projects.
pub list_projects: Arc<ListProjects>,
/// Open a terminal (spawn PTY, register session).
pub open_terminal: Arc<OpenTerminal>,
/// Write keystrokes to a terminal.
pub write_terminal: Arc<WriteToTerminal>,
/// Resize a terminal.
pub resize_terminal: Arc<ResizeTerminal>,
/// Close a terminal (kill PTY).
pub close_terminal: Arc<CloseTerminal>,
/// Load a project's persisted layout tree.
pub load_layout: Arc<LoadLayout>,
/// Mutate + persist a project's layout tree.
pub mutate_layout: Arc<MutateLayout>,
/// List all named layouts for a project (#4).
pub list_layouts: Arc<ListLayouts>,
/// Create a new named layout (#4).
pub create_layout: Arc<CreateLayout>,
/// Rename a named layout (#4).
pub rename_layout: Arc<RenameLayout>,
/// Delete a named layout (#4).
pub delete_layout: Arc<DeleteLayout>,
/// Set the active named layout (#4).
pub set_active_layout: Arc<SetActiveLayout>,
/// Detect which candidate profiles' CLIs are installed (first-run).
pub detect_profiles: Arc<DetectProfiles>,
/// List configured profiles.
pub list_profiles: Arc<ListProfiles>,
/// Save (upsert) a profile.
pub save_profile: Arc<SaveProfile>,
/// Delete a profile.
pub delete_profile: Arc<DeleteProfile>,
/// Persist the batch of chosen profiles (closes the first run).
pub configure_profiles: Arc<ConfigureProfiles>,
/// Expose the pre-filled reference catalogue.
pub reference_profiles: Arc<ReferenceProfiles>,
/// Whether the first-run wizard should show + the reference catalogue.
pub first_run_state: Arc<FirstRunState>,
/// The local PTY adapter, kept port-typed so the presentation layer can
/// `subscribe_output` to pump bytes into the [`PtyBridge`] (it owns transport).
pub pty_port: Arc<dyn PtyPort>,
/// Active-terminal registry shared by the terminal use cases.
pub terminal_sessions: Arc<TerminalSessions>,
/// The domain event bus (also handed to the event relay).
pub event_bus: Arc<TokioBroadcastEventBus>,
/// Generic PTY↔Channel bridge registry (consumed by L3).
pub pty_bridge: Arc<PtyBridge>,
// --- Agents (L6) ---
/// Create a project agent from scratch.
pub create_agent: Arc<CreateAgentFromScratch>,
/// List a project's agents.
pub list_agents: Arc<ListAgents>,
/// Read an agent's Markdown context.
pub read_agent_context: Arc<ReadAgentContext>,
/// Overwrite an agent's Markdown context.
pub update_agent_context: Arc<UpdateAgentContext>,
/// Delete an agent from the manifest.
pub delete_agent: Arc<DeleteAgent>,
/// Launch an agent (spawn PTY, apply injection strategy).
pub launch_agent: Arc<LaunchAgent>,
/// Project registry — used by agent commands to resolve a `Project` from an id.
pub project_store: Arc<dyn ProjectStore>,
// --- Windows (L10) ---
/// Detach a tab into a new OS window (persists the workspace topology).
pub move_tab: Arc<MoveTabToNewWindow>,
// --- Templates & sync (L7) ---
/// Create a template in the global store.
pub create_template: Arc<CreateTemplate>,
/// Update a template's content (bumps version).
pub update_template: Arc<UpdateTemplate>,
/// List all templates in the global store.
pub list_templates: Arc<ListTemplates>,
/// Delete a template from the global store.
pub delete_template: Arc<DeleteTemplate>,
/// Create an agent from a template.
pub create_agent_from_template: Arc<CreateAgentFromTemplate>,
/// Detect which synchronized agents are behind their template.
pub detect_agent_drift: Arc<DetectAgentDrift>,
/// Apply a template update to a synchronized agent.
pub sync_agent_with_template: Arc<SyncAgentWithTemplate>,
// --- Git (L8) ---
/// Report the working-tree status of a repository.
pub git_status: Arc<GitStatus>,
/// Stage a path.
pub git_stage: Arc<GitStage>,
/// Unstage a path.
pub git_unstage: Arc<GitUnstage>,
/// Create a commit.
pub git_commit: Arc<GitCommit>,
/// List branches.
pub git_branches: Arc<GitBranches>,
/// Check out a branch.
pub git_checkout: Arc<GitCheckout>,
/// Return the recent commit log.
pub git_log: Arc<GitLog>,
/// Initialise a repository.
pub git_init: Arc<GitInit>,
/// Return the commit graph for all local branches.
pub git_graph: Arc<GitGraph>,
}
impl AppState {
/// **Composition root.** Builds all adapters and use cases.
///
/// `app_data_dir` is the machine-local IDE data directory (ARCHITECTURE
/// §9.2), resolved by the caller via the Tauri path API and injected here so
/// the stores never touch Tauri themselves (Dependency Inversion).
///
/// This is the only function that constructs concrete adapters; every other
/// layer depends on ports. Adapters added in later lots (PTY, git, remote)
/// are wired in here.
#[must_use]
pub fn build(app_data_dir: PathBuf) -> Self {
// --- Concrete adapters (driven adapters) ---
let event_bus = Arc::new(TokioBroadcastEventBus::new());
let clock = Arc::new(SystemClock::new());
let ids = Arc::new(UuidGenerator::new());
let fs = Arc::new(LocalFileSystem::new());
let store = Arc::new(FsProjectStore::new(
Arc::clone(&fs) as Arc<dyn FileSystem>,
app_data_dir.to_string_lossy().into_owned(),
));
// Port-typed handles for injection.
let fs_port = Arc::clone(&fs) as Arc<dyn FileSystem>;
let store_port = Arc::clone(&store) as Arc<dyn ProjectStore>;
let events_port = Arc::clone(&event_bus) as Arc<dyn EventBus>;
// --- Use cases (ports injected as Arc<dyn Port>) ---
let health = Arc::new(HealthUseCase::new(
Arc::clone(&clock) as Arc<dyn Clock>,
Arc::clone(&ids) as Arc<dyn IdGenerator>,
Arc::clone(&events_port),
));
let create_project = Arc::new(CreateProject::new(
Arc::clone(&store_port),
Arc::clone(&fs_port),
Arc::clone(&ids) as Arc<dyn IdGenerator>,
Arc::clone(&clock) as Arc<dyn Clock>,
Arc::clone(&events_port),
));
let open_project = Arc::new(OpenProject::new(
Arc::clone(&store_port),
Arc::clone(&fs_port),
));
let close_project = Arc::new(CloseProject::new(Arc::clone(&store_port)));
let close_tab = Arc::new(CloseTab::new(Arc::clone(&store_port)));
let list_projects = Arc::new(ListProjects::new(Arc::clone(&store_port)));
// --- PTY adapter + terminal use cases (L3) ---
let pty = Arc::new(PortablePtyAdapter::new());
let pty_port = Arc::clone(&pty) as Arc<dyn PtyPort>;
let terminal_sessions = Arc::new(TerminalSessions::new());
let open_terminal = Arc::new(OpenTerminal::new(
Arc::clone(&pty_port),
Arc::clone(&terminal_sessions),
Arc::clone(&events_port),
));
let write_terminal = Arc::new(WriteToTerminal::new(
Arc::clone(&pty_port),
Arc::clone(&terminal_sessions),
));
let resize_terminal = Arc::new(ResizeTerminal::new(
Arc::clone(&pty_port),
Arc::clone(&terminal_sessions),
));
let close_terminal = Arc::new(CloseTerminal::new(
Arc::clone(&pty_port),
Arc::clone(&terminal_sessions),
));
// --- Layout use cases (L4 + #4) ---
let load_layout = Arc::new(LoadLayout::new(
Arc::clone(&store_port),
Arc::clone(&fs_port),
));
let mutate_layout = Arc::new(MutateLayout::new(
Arc::clone(&store_port),
Arc::clone(&fs_port),
Arc::clone(&events_port),
));
let list_layouts = Arc::new(ListLayouts::new(
Arc::clone(&store_port),
Arc::clone(&fs_port),
));
let create_layout = Arc::new(CreateLayout::new(
Arc::clone(&store_port),
Arc::clone(&fs_port),
Arc::clone(&ids) as Arc<dyn IdGenerator>,
Arc::clone(&events_port),
));
let rename_layout = Arc::new(RenameLayout::new(
Arc::clone(&store_port),
Arc::clone(&fs_port),
Arc::clone(&events_port),
));
let delete_layout = Arc::new(DeleteLayout::new(
Arc::clone(&store_port),
Arc::clone(&fs_port),
Arc::clone(&events_port),
));
let set_active_layout = Arc::new(SetActiveLayout::new(
Arc::clone(&store_port),
Arc::clone(&fs_port),
Arc::clone(&events_port),
));
// --- Profiles & AI runtime (L5) ---
// One generic, profile-driven runtime adapter (Open/Closed): it holds the
// process spawner used for detection. The profile store persists
// `profiles.json` in the same machine-local app-data dir as the project
// registry.
let spawner = Arc::new(LocalProcessSpawner::new());
let spawner_port = Arc::clone(&spawner) as Arc<dyn ProcessSpawner>;
let runtime = Arc::new(CliAgentRuntime::new(Arc::clone(&spawner_port)));
let runtime_port = Arc::clone(&runtime) as Arc<dyn AgentRuntime>;
let profile_store = Arc::new(FsProfileStore::new(
Arc::clone(&fs_port),
app_data_dir.to_string_lossy().into_owned(),
));
let profile_store_port = Arc::clone(&profile_store) as Arc<dyn ProfileStore>;
let detect_profiles = Arc::new(DetectProfiles::new(Arc::clone(&runtime_port)));
let list_profiles = Arc::new(ListProfiles::new(Arc::clone(&profile_store_port)));
let save_profile = Arc::new(SaveProfile::new(Arc::clone(&profile_store_port)));
let delete_profile = Arc::new(DeleteProfile::new(Arc::clone(&profile_store_port)));
let configure_profiles = Arc::new(ConfigureProfiles::new(Arc::clone(&profile_store_port)));
let reference_profiles = Arc::new(ReferenceProfiles::new());
let first_run_state = Arc::new(FirstRunState::new(Arc::clone(&profile_store_port)));
let pty_bridge = Arc::new(PtyBridge::new());
// --- Agent context store + use cases (L6) ---
let contexts = Arc::new(IdeaiContextStore::new(Arc::clone(&fs_port)));
let contexts_port = Arc::clone(&contexts) as Arc<dyn AgentContextStore>;
let create_agent = Arc::new(CreateAgentFromScratch::new(
Arc::clone(&contexts_port),
Arc::clone(&ids) as Arc<dyn IdGenerator>,
Arc::clone(&events_port),
));
let list_agents = Arc::new(ListAgents::new(Arc::clone(&contexts_port)));
let read_agent_context = Arc::new(ReadAgentContext::new(Arc::clone(&contexts_port)));
let update_agent_context = Arc::new(UpdateAgentContext::new(Arc::clone(&contexts_port)));
let delete_agent = Arc::new(DeleteAgent::new(
Arc::clone(&contexts_port),
Arc::clone(&events_port),
));
// LaunchAgent shares the SAME pty_port and terminal_sessions as the terminal
// use cases — indispensable for the PtyBridge to work correctly.
let launch_agent = Arc::new(LaunchAgent::new(
Arc::clone(&contexts_port),
Arc::clone(&profile_store_port),
Arc::clone(&runtime_port),
Arc::clone(&fs_port),
Arc::clone(&pty_port),
Arc::clone(&terminal_sessions),
Arc::clone(&events_port),
));
let project_store = Arc::clone(&store_port);
// --- Template store + use cases (L7) ---
let template_store = Arc::new(FsTemplateStore::new(
Arc::clone(&fs_port),
app_data_dir.to_string_lossy().into_owned(),
));
let template_store_port = Arc::clone(&template_store) as Arc<dyn TemplateStore>;
let create_template = Arc::new(CreateTemplate::new(
Arc::clone(&template_store_port),
Arc::clone(&ids) as Arc<dyn IdGenerator>,
));
let update_template = Arc::new(UpdateTemplate::new(
Arc::clone(&template_store_port),
Arc::clone(&events_port),
));
let list_templates = Arc::new(ListTemplates::new(Arc::clone(&template_store_port)));
let delete_template = Arc::new(DeleteTemplate::new(Arc::clone(&template_store_port)));
let create_agent_from_template = Arc::new(CreateAgentFromTemplate::new(
Arc::clone(&template_store_port),
Arc::clone(&contexts_port),
Arc::clone(&ids) as Arc<dyn IdGenerator>,
Arc::clone(&events_port),
));
let detect_agent_drift = Arc::new(DetectAgentDrift::new(
Arc::clone(&template_store_port),
Arc::clone(&contexts_port),
Arc::clone(&events_port),
));
let sync_agent_with_template = Arc::new(SyncAgentWithTemplate::new(
Arc::clone(&template_store_port),
Arc::clone(&contexts_port),
Arc::clone(&events_port),
));
// --- Git adapter + use cases (L8) ---
let git = Arc::new(Git2Repository::new());
let git_port = Arc::clone(&git) as Arc<dyn GitPort>;
let git_status = Arc::new(GitStatus::new(Arc::clone(&git_port)));
let git_stage = Arc::new(GitStage::new(Arc::clone(&git_port)));
let git_unstage = Arc::new(GitUnstage::new(Arc::clone(&git_port)));
let git_commit = Arc::new(GitCommit::new(Arc::clone(&git_port), Arc::clone(&events_port)));
let git_branches = Arc::new(GitBranches::new(Arc::clone(&git_port)));
let git_checkout = Arc::new(GitCheckout::new(
Arc::clone(&git_port),
Arc::clone(&events_port),
));
let git_log = Arc::new(GitLog::new(Arc::clone(&git_port)));
let git_init = Arc::new(GitInit::new(Arc::clone(&git_port), Arc::clone(&events_port)));
let git_graph = Arc::new(GitGraph::new(Arc::clone(&git_port)));
// --- Windows (L10) ---
let move_tab = Arc::new(MoveTabToNewWindow::new(
Arc::clone(&store_port),
Arc::clone(&ids) as Arc<dyn IdGenerator>,
));
Self {
health,
create_project,
open_project,
close_project,
close_tab,
list_projects,
open_terminal,
write_terminal,
resize_terminal,
close_terminal,
load_layout,
mutate_layout,
list_layouts,
create_layout,
rename_layout,
delete_layout,
set_active_layout,
detect_profiles,
list_profiles,
save_profile,
delete_profile,
configure_profiles,
reference_profiles,
first_run_state,
pty_port,
terminal_sessions,
event_bus,
pty_bridge,
create_agent,
list_agents,
read_agent_context,
update_agent_context,
delete_agent,
launch_agent,
project_store,
create_template,
update_template,
list_templates,
delete_template,
create_agent_from_template,
detect_agent_drift,
sync_agent_with_template,
git_status,
git_stage,
git_unstage,
git_commit,
git_branches,
git_checkout,
git_log,
git_init,
git_graph,
move_tab,
}
}
}