#![allow(dead_code)]
use std::fs;
use std::path::Path;
use clap::Args as ClapArgs;
use crate::claude_skills;
use crate::defaults;
use crate::distro;
use crate::error::CliError;
use crate::fetch;
use crate::github::GithubClient;
use crate::manifest;
use crate::scaffold;
use crate::tarball;
#[derive(Debug, ClapArgs)]
pub struct Args {
#[arg(long_help = "Distro specifier.\n\
\n\
Accepted forms:\n\
bare name: omne-nosce\n\
org/repo: omne-org/omne-nosce\n\
HTTPS URL: https://github.com/omne-org/omne-nosce.git\n\
SSH URL: git@github.com:omne-org/omne-nosce.git\n\
\n\
file:// URLs and non-github.com hosts are rejected.")]
pub distro: String,
}
pub fn run(args: &Args) -> Result<(), CliError> {
let root = std::env::current_dir()
.map_err(|e| CliError::Io(format!("cannot determine current directory: {e}")))?;
let github = GithubClient::from_env("https://api.github.com", "omne-cli");
init_with_client(&args.distro, &root, &github)
}
pub fn init_with_client(
distro_spec: &str,
root: &Path,
github: &GithubClient,
) -> Result<(), CliError> {
let spec = distro::parse(distro_spec)?;
let omne = root.join(".omne");
if omne.exists() {
return Err(CliError::VolumeAlreadyExists { path: omne });
}
claude_skills::preflight()?;
scaffold::create_volume_dirs(root)?;
scaffold::write_docs_baseline(root)?;
scaffold::write_gitignore(root)?;
let (kernel_org, kernel_repo) = parse_source(defaults::DEFAULT_KERNEL_SOURCE);
let kernel_tag = github.latest_release_tag(kernel_org, kernel_repo)?;
fetch::download_and_extract(github, kernel_org, kernel_repo, &kernel_tag, &omne, "core")?;
let distro_tag = github.latest_release_tag(&spec.org, &spec.repo)?;
fetch::download_and_extract(github, &spec.org, &spec.repo, &distro_tag, &omne, "dist")?;
stamp_and_finalize(root, &omne, &spec)
}
pub fn init_with_tarballs(
distro_spec: &str,
root: &Path,
kernel_tarball: &Path,
distro_tarball: &Path,
) -> Result<(), CliError> {
let spec = distro::parse(distro_spec)?;
let omne = root.join(".omne");
if omne.exists() {
return Err(CliError::VolumeAlreadyExists { path: omne });
}
claude_skills::preflight()?;
scaffold::create_volume_dirs(root)?;
scaffold::write_docs_baseline(root)?;
scaffold::write_gitignore(root)?;
let kernel_file = fs::File::open(kernel_tarball)?;
tarball::extract_safe(kernel_file, &omne)?;
verify_top_level(&omne, "core")?;
let distro_file = fs::File::open(distro_tarball)?;
tarball::extract_safe(distro_file, &omne)?;
verify_top_level(&omne, "dist")?;
stamp_and_finalize(root, &omne, &spec)
}
fn stamp_and_finalize(root: &Path, omne: &Path, spec: &distro::DistroSpec) -> Result<(), CliError> {
let (distro_name, distro_version) = read_distro_metadata(&omne.join("dist"));
let volume_name = root
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| "unknown".to_string());
let today = chrono_today();
let vars = manifest::Vars {
volume: volume_name,
distro: distro_name.clone(),
distro_version,
created: today,
kernel_source: defaults::DEFAULT_KERNEL_SOURCE.to_string(),
distro_source: format!("{}/{}", spec.org, spec.repo),
};
let stamped = manifest::stamp(&vars);
scaffold::write_omne_readme(root, &stamped)?;
scaffold::write_bootloader(root)?;
claude_skills::link_skills(root)?;
eprintln!(
"\x1b[32m✓\x1b[0m Initialized omne volume '{}' with distro '{}'",
vars.volume, distro_name
);
Ok(())
}
fn parse_source(source: &str) -> (&str, &str) {
let (org, repo) = source.split_once('/').expect("source must be org/repo");
(org, repo)
}
fn read_distro_metadata(dist_dir: &Path) -> (String, String) {
let manifest_path = dist_dir.join("manifest.json");
if manifest_path.is_file() {
if let Ok(content) = fs::read_to_string(&manifest_path) {
if let Ok(data) = serde_json::from_str::<serde_json::Value>(&content) {
let name = data
.get("name")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string();
let version = data
.get("version")
.and_then(|v| v.as_str())
.unwrap_or("0.0.0")
.to_string();
return (name, version);
}
}
}
("unknown".to_string(), "0.0.0".to_string())
}
fn verify_top_level(target: &Path, expected: &str) -> Result<(), CliError> {
if !target.join(expected).is_dir() {
let found: Vec<String> = fs::read_dir(target)
.ok()
.map(|entries| {
entries
.filter_map(|e| e.ok())
.map(|e| e.file_name().to_string_lossy().into_owned())
.collect()
})
.unwrap_or_default();
return Err(CliError::TarballLayoutMismatch {
expected: expected.to_string(),
found,
});
}
Ok(())
}
fn chrono_today() -> String {
crate::clock::now_utc().format_date()
}