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,102 @@
//! [`LocalFileSystem`] — minimal [`FileSystem`] adapter over [`tokio::fs`].
//!
//! Maps [`RemotePath`] (a location-neutral string path) onto the local OS
//! filesystem. Only the operations needed to wire up the composition root and
//! later lots are implemented; richer behaviour (and SSH/WSL siblings) arrives
//! in L2/L9.
use std::io;
use std::path::Path;
use async_trait::async_trait;
use domain::ports::{DirEntry, FileSystem, FsError, RemotePath};
use tokio::fs;
/// Filesystem adapter backed by the local OS via `tokio::fs`.
#[derive(Debug, Default, Clone, Copy)]
pub struct LocalFileSystem;
impl LocalFileSystem {
/// Creates a new [`LocalFileSystem`].
#[must_use]
pub const fn new() -> Self {
Self
}
}
/// Maps a [`std::io::Error`] to the domain's [`FsError`], preserving the
/// not-found / permission distinctions the application layer cares about.
fn map_io(path: &RemotePath, err: &io::Error) -> FsError {
match err.kind() {
io::ErrorKind::NotFound => FsError::NotFound(path.as_str().to_owned()),
io::ErrorKind::PermissionDenied => FsError::PermissionDenied(path.as_str().to_owned()),
_ => FsError::Io(format!("{}: {err}", path.as_str())),
}
}
#[async_trait]
impl FileSystem for LocalFileSystem {
async fn read(&self, path: &RemotePath) -> Result<Vec<u8>, FsError> {
fs::read(path.as_str())
.await
.map_err(|e| map_io(path, &e))
}
async fn write(&self, path: &RemotePath, data: &[u8]) -> Result<(), FsError> {
fs::write(path.as_str(), data)
.await
.map_err(|e| map_io(path, &e))
}
async fn exists(&self, path: &RemotePath) -> Result<bool, FsError> {
match fs::metadata(path.as_str()).await {
Ok(_) => Ok(true),
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
Err(e) => Err(map_io(path, &e)),
}
}
async fn create_dir_all(&self, path: &RemotePath) -> Result<(), FsError> {
fs::create_dir_all(path.as_str())
.await
.map_err(|e| map_io(path, &e))
}
async fn list(&self, path: &RemotePath) -> Result<Vec<DirEntry>, FsError> {
let mut entries = Vec::new();
let mut read_dir = fs::read_dir(path.as_str())
.await
.map_err(|e| map_io(path, &e))?;
while let Some(entry) = read_dir.next_entry().await.map_err(|e| map_io(path, &e))? {
let is_dir = entry
.file_type()
.await
.map(|t| t.is_dir())
.unwrap_or(false);
entries.push(DirEntry {
name: entry.file_name().to_string_lossy().into_owned(),
is_dir,
});
}
Ok(entries)
}
async fn symlink(&self, src: &RemotePath, dst: &RemotePath) -> Result<(), FsError> {
symlink_impl(Path::new(src.as_str()), Path::new(dst.as_str()))
.await
.map_err(|e| map_io(dst, &e))
}
}
#[cfg(unix)]
async fn symlink_impl(src: &Path, dst: &Path) -> io::Result<()> {
fs::symlink(src, dst).await
}
#[cfg(windows)]
async fn symlink_impl(src: &Path, dst: &Path) -> io::Result<()> {
// On Windows we default to a file symlink; directory symlinks require a
// different call and are handled when the store layer (L2/L6) needs them.
fs::symlink_file(src, dst).await
}