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

View File

@ -0,0 +1,323 @@
//! Profile use cases (ARCHITECTURE §6, L5). Each is a single-responsibility
//! struct carrying its ports as `Arc<dyn Port>` and exposing one `execute`.
//!
//! - [`DetectProfiles`] — probe a set of candidate profiles via [`AgentRuntime`]
//! and report which CLIs are installed (first-run availability ✓/✗).
//! - [`ListProfiles`] / [`SaveProfile`] / [`DeleteProfile`] — CRUD over the
//! persisted profiles through the [`ProfileStore`].
//! - [`ConfigureProfiles`] — persist a batch of chosen/edited/custom profiles
//! (closes the first-run wizard).
//! - [`ReferenceProfiles`] — expose the pre-filled, editable catalogue.
//! - [`FirstRunState`] — tell the UI whether the first-run wizard should show
//! (no `profiles.json` yet) and hand it the reference catalogue.
use std::sync::Arc;
use domain::ports::{AgentRuntime, ProfileStore};
use domain::profile::AgentProfile;
use crate::error::AppError;
use super::catalogue::reference_profiles;
// ---------------------------------------------------------------------------
// DetectProfiles
// ---------------------------------------------------------------------------
/// Input for [`DetectProfiles::execute`]: the candidate profiles to probe.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DetectProfilesInput {
/// Profiles whose `detect` command should be run.
pub candidates: Vec<AgentProfile>,
}
/// Availability of a single candidate after detection.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProfileAvailability {
/// The probed profile.
pub profile: AgentProfile,
/// Whether its CLI was detected as installed (exit code 0).
pub available: bool,
}
/// Output of [`DetectProfiles::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DetectProfilesOutput {
/// One entry per candidate (same order), with its availability.
pub results: Vec<ProfileAvailability>,
}
/// Probes candidate profiles' detection commands and reports availability.
pub struct DetectProfiles {
runtime: Arc<dyn AgentRuntime>,
}
impl DetectProfiles {
/// Builds the use case from the [`AgentRuntime`] port. The runtime itself
/// holds the [`domain::ports::ProcessSpawner`] used for detection.
#[must_use]
pub fn new(runtime: Arc<dyn AgentRuntime>) -> Self {
Self { runtime }
}
/// Runs detection for each candidate. A detection *error* (e.g. the command
/// could not even be launched) is reported as `available: false`, not a
/// hard failure — the wizard just shows ✗ and the user can still keep the
/// profile.
///
/// # Errors
/// Currently never returns `Err` (failures degrade to `available: false`);
/// the `Result` keeps the signature uniform with the other use cases.
pub async fn execute(
&self,
input: DetectProfilesInput,
) -> Result<DetectProfilesOutput, AppError> {
let results = input
.candidates
.into_iter()
.map(|profile| {
let available = self.runtime.detect(&profile).unwrap_or(false);
ProfileAvailability { profile, available }
})
.collect();
Ok(DetectProfilesOutput { results })
}
}
// ---------------------------------------------------------------------------
// ListProfiles
// ---------------------------------------------------------------------------
/// Output of [`ListProfiles::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListProfilesOutput {
/// All configured profiles.
pub profiles: Vec<AgentProfile>,
}
/// Lists the configured profiles from the store.
pub struct ListProfiles {
store: Arc<dyn ProfileStore>,
}
impl ListProfiles {
/// Builds the use case from the [`ProfileStore`] port.
#[must_use]
pub fn new(store: Arc<dyn ProfileStore>) -> Self {
Self { store }
}
/// Lists configured profiles.
///
/// # Errors
/// [`AppError::Store`] on persistence failure.
pub async fn execute(&self) -> Result<ListProfilesOutput, AppError> {
Ok(ListProfilesOutput {
profiles: self.store.list().await?,
})
}
}
// ---------------------------------------------------------------------------
// SaveProfile
// ---------------------------------------------------------------------------
/// Input for [`SaveProfile::execute`]: the profile to upsert.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SaveProfileInput {
/// The profile to create or replace (by id).
pub profile: AgentProfile,
}
/// Output of [`SaveProfile::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SaveProfileOutput {
/// The saved profile (echoed back).
pub profile: AgentProfile,
}
/// Persists (creates or replaces) a single profile.
pub struct SaveProfile {
store: Arc<dyn ProfileStore>,
}
impl SaveProfile {
/// Builds the use case from the [`ProfileStore`] port.
#[must_use]
pub fn new(store: Arc<dyn ProfileStore>) -> Self {
Self { store }
}
/// Saves the profile.
///
/// # Errors
/// [`AppError::Store`] on persistence failure.
pub async fn execute(&self, input: SaveProfileInput) -> Result<SaveProfileOutput, AppError> {
self.store.save(&input.profile).await?;
Ok(SaveProfileOutput {
profile: input.profile,
})
}
}
// ---------------------------------------------------------------------------
// DeleteProfile
// ---------------------------------------------------------------------------
/// Input for [`DeleteProfile::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeleteProfileInput {
/// Id of the profile to delete.
pub id: domain::ids::ProfileId,
}
/// Deletes a profile by id.
pub struct DeleteProfile {
store: Arc<dyn ProfileStore>,
}
impl DeleteProfile {
/// Builds the use case from the [`ProfileStore`] port.
#[must_use]
pub fn new(store: Arc<dyn ProfileStore>) -> Self {
Self { store }
}
/// Deletes the profile.
///
/// # Errors
/// [`AppError::NotFound`] if the id is unknown, [`AppError::Store`] on
/// persistence failure.
pub async fn execute(&self, input: DeleteProfileInput) -> Result<(), AppError> {
self.store.delete(input.id).await?;
Ok(())
}
}
// ---------------------------------------------------------------------------
// ConfigureProfiles
// ---------------------------------------------------------------------------
/// Input for [`ConfigureProfiles::execute`]: the chosen/edited/custom profiles.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConfigureProfilesInput {
/// All profiles the user decided to keep (closes the first run).
pub profiles: Vec<AgentProfile>,
}
/// Output of [`ConfigureProfiles::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConfigureProfilesOutput {
/// The persisted profiles.
pub profiles: Vec<AgentProfile>,
}
/// Persists the batch of profiles chosen at the end of the first-run wizard.
///
/// Saving even an empty list creates `profiles.json`, which marks the first run
/// as done (so the wizard does not reappear).
pub struct ConfigureProfiles {
store: Arc<dyn ProfileStore>,
}
impl ConfigureProfiles {
/// Builds the use case from the [`ProfileStore`] port.
#[must_use]
pub fn new(store: Arc<dyn ProfileStore>) -> Self {
Self { store }
}
/// Persists each chosen profile.
///
/// # Errors
/// [`AppError::Store`] on persistence failure.
pub async fn execute(
&self,
input: ConfigureProfilesInput,
) -> Result<ConfigureProfilesOutput, AppError> {
for profile in &input.profiles {
self.store.save(profile).await?;
}
// Ensure `profiles.json` exists even when the user kept nothing, so the
// first run is recorded as complete.
if input.profiles.is_empty() {
self.store.mark_configured().await?;
}
Ok(ConfigureProfilesOutput {
profiles: input.profiles,
})
}
}
// ---------------------------------------------------------------------------
// ReferenceProfiles (catalogue accessor)
// ---------------------------------------------------------------------------
/// Output of [`ReferenceProfiles::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReferenceProfilesOutput {
/// The pre-filled, editable reference catalogue.
pub profiles: Vec<AgentProfile>,
}
/// Exposes the pre-filled reference catalogue (Claude/Codex/Gemini/Aider).
#[derive(Default)]
pub struct ReferenceProfiles;
impl ReferenceProfiles {
/// Builds the (stateless) use case.
#[must_use]
pub fn new() -> Self {
Self
}
/// Returns the reference catalogue. Infallible.
///
/// # Errors
/// Never; the `Result` keeps the call site uniform.
#[allow(clippy::unused_async)]
pub async fn execute(&self) -> Result<ReferenceProfilesOutput, AppError> {
Ok(ReferenceProfilesOutput {
profiles: reference_profiles(),
})
}
}
// ---------------------------------------------------------------------------
// FirstRunState
// ---------------------------------------------------------------------------
/// Output of [`FirstRunState::execute`]: whether to show the wizard + catalogue.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FirstRunStateOutput {
/// `true` when no `profiles.json` exists yet ⇒ show the first-run wizard.
pub is_first_run: bool,
/// The pre-filled reference catalogue to seed the wizard.
pub reference_profiles: Vec<AgentProfile>,
}
/// Reports whether the IDE is on its first run (no profiles configured yet) and
/// provides the reference catalogue to seed the wizard.
pub struct FirstRunState {
store: Arc<dyn ProfileStore>,
}
impl FirstRunState {
/// Builds the use case from the [`ProfileStore`] port.
#[must_use]
pub fn new(store: Arc<dyn ProfileStore>) -> Self {
Self { store }
}
/// Computes the first-run state.
///
/// # Errors
/// [`AppError::Store`] on persistence failure.
pub async fn execute(&self) -> Result<FirstRunStateOutput, AppError> {
let configured = self.store.is_configured().await?;
Ok(FirstRunStateOutput {
is_first_run: !configured,
reference_profiles: reference_profiles(),
})
}
}