use core::fmt::Write as _;
use unilang::data::{ ErrorCode, ErrorData, OutputData };
use unilang::interpreter::ExecutionContext;
use unilang::semantic::VerifiedCommand;
use unilang::types::Value;
use claude_assets_core::artifact::ArtifactKind;
use claude_assets_core::error::AssetError;
use claude_assets_core::install::{ InstallAction, install, uninstall };
use claude_assets_core::paths::{ AssetPaths, AssetPathsError };
use claude_assets_core::registry::{ InstallStatus, list_all };
fn asset_err_to_error_data( e : &AssetError ) -> ErrorData
{
ErrorData::new( ErrorCode::InternalError, e.to_string() )
}
fn paths_err_to_error_data( e : &AssetPathsError ) -> ErrorData
{
ErrorData::new( ErrorCode::InternalError, e.to_string() )
}
fn opt_str( cmd : &VerifiedCommand, name : &str ) -> String
{
match cmd.arguments.get( name )
{
Some( Value::String( s ) ) => s.clone(),
_ => String::new(),
}
}
fn parse_kind( raw : &str ) -> Result< ArtifactKind, ErrorData >
{
ArtifactKind::from_name( raw ).ok_or_else( ||
{
let valid : Vec< &str > = ArtifactKind::all().iter().map( |k| k.as_str() ).collect();
ErrorData::new(
ErrorCode::ArgumentTypeMismatch,
format!( "unknown kind '{raw}': valid kinds are {}", valid.join( ", " ) ),
)
} )
}
fn require_str( cmd : &VerifiedCommand, name : &str ) -> Result< String, ErrorData >
{
let val = opt_str( cmd, name );
if val.is_empty()
{
Err( ErrorData::new( ErrorCode::ArgumentMissing, format!( "{name}:: is required" ) ) )
}
else
{
Ok( val )
}
}
#[ allow( clippy::needless_pass_by_value ) ]
#[ inline ]
pub fn list_routine( cmd : VerifiedCommand, _ctx : ExecutionContext ) -> Result< OutputData, ErrorData >
{
let paths = AssetPaths::from_env().map_err( |e| paths_err_to_error_data( &e ) )?;
let raw_kind = opt_str( &cmd, "kind" );
let installed_only = matches!( cmd.arguments.get( "installed" ), Some( Value::Boolean( true ) ) );
let kinds : Vec< ArtifactKind > = if raw_kind.is_empty()
{
ArtifactKind::all().to_vec()
}
else
{
vec![ parse_kind( &raw_kind )? ]
};
let mut out = String::new();
for kind in kinds
{
let entries = list_all( &paths, kind ).map_err( |e| asset_err_to_error_data( &e ) )?;
if entries.is_empty()
{
continue;
}
for ( name, status ) in &entries
{
if installed_only && *status != InstallStatus::Installed
{
continue;
}
let marker = if *status == InstallStatus::Installed { '●' } else { '○' };
let kind_str = kind.as_str();
writeln!( out, "{marker} {kind_str}/{name}" ).expect( "string write" );
}
}
if out.is_empty()
{
out.push_str( "No artifacts found.\n" );
}
Ok( OutputData::new( out, "text" ) )
}
#[ allow( clippy::needless_pass_by_value ) ]
#[ inline ]
pub fn install_routine( cmd : VerifiedCommand, _ctx : ExecutionContext ) -> Result< OutputData, ErrorData >
{
let paths = AssetPaths::from_env().map_err( |e| paths_err_to_error_data( &e ) )?;
let raw_kind = require_str( &cmd, "kind" )?;
let name = require_str( &cmd, "name" )?;
let kind = parse_kind( &raw_kind )?;
let report = install( &paths, kind, &name ).map_err( |e| asset_err_to_error_data( &e ) )?;
let kind_str = kind.as_str();
let msg = match report.action
{
InstallAction::Installed => format!( "Installed {kind_str}/{name}\n" ),
InstallAction::Reinstalled => format!( "Reinstalled {kind_str}/{name}\n" ),
InstallAction::Uninstalled => format!( "Uninstalled {kind_str}/{name}\n" ),
InstallAction::NotInstalled => format!( "Not installed: {kind_str}/{name}\n" ),
};
Ok( OutputData::new( msg, "text" ) )
}
#[ allow( clippy::needless_pass_by_value ) ]
#[ inline ]
pub fn uninstall_routine( cmd : VerifiedCommand, _ctx : ExecutionContext ) -> Result< OutputData, ErrorData >
{
let paths = AssetPaths::from_env().map_err( |e| paths_err_to_error_data( &e ) )?;
let raw_kind = require_str( &cmd, "kind" )?;
let name = require_str( &cmd, "name" )?;
let kind = parse_kind( &raw_kind )?;
let report = uninstall( &paths, kind, &name ).map_err( |e| asset_err_to_error_data( &e ) )?;
let kind_str = kind.as_str();
let msg = match report.action
{
InstallAction::Uninstalled => format!( "Uninstalled {kind_str}/{name}\n" ),
InstallAction::NotInstalled => format!( "Not installed: {kind_str}/{name}\n" ),
InstallAction::Installed => format!( "Installed {kind_str}/{name}\n" ),
InstallAction::Reinstalled => format!( "Reinstalled {kind_str}/{name}\n" ),
};
Ok( OutputData::new( msg, "text" ) )
}
#[ allow( clippy::needless_pass_by_value ) ]
#[ inline ]
pub fn kinds_routine( _cmd : VerifiedCommand, _ctx : ExecutionContext ) -> Result< OutputData, ErrorData >
{
let source_root = match AssetPaths::from_env()
{
Ok( p ) => p.source_root().to_string_lossy().into_owned(),
Err( _ ) => "$PRO_CLAUDE".to_string(),
};
let mut out = String::new();
for kind in ArtifactKind::all()
{
let kind_str = kind.as_str();
let src = kind.source_subdir();
let tgt = kind.target_subdir();
writeln!( out, "{kind_str:<10} {source_root}/{src} → .claude/{tgt}" ).expect( "string write" );
}
Ok( OutputData::new( out, "text" ) )
}