use clap::{Parser, Subcommand}; use craftlabs_auth_core::{ craft_activate, craft_check_license, craft_destroy, craft_free_license_info, craft_get_license_info, craft_heartbeat, craft_initialize, craft_release, }; use craftlabs_auth_core::device; use std::ffi::CString; use std::path::PathBuf; use std::fs; mod config; mod platform_api; use config::Config; use platform_api::PlatformClient; #[derive(Parser)] #[command(name = "craft", version, about = "CraftLabs 授权客户端")] struct Cli { #[command(subcommand)] command: Commands, /// 平台 API 地址 (默认: http://localhost:8080) #[arg(long, global = true)] api: Option, /// JSON 格式输出 #[arg(long, global = true)] json: bool, } #[derive(Subcommand)] enum Commands { /// 查看本地授权状态 Status, /// 使用 SN 激活本机 Activate { sn: String }, /// 检查授权是否有效 Check, /// 显示授权详情 Info, /// 撤销本机授权 Release, /// 迁移授权到本机 (释放旧授权 + 激活新 SN) Migrate { sn: String }, /// 显示本机设备指纹 DeviceId, /// 手动触发心跳 Heartbeat, /// 查看/修改配置 Config { /// 查看或设置: status, set-api , set-sn action: Vec, }, } fn get_config_path() -> PathBuf { let mut path = dirs::config_dir().unwrap_or_else(|| PathBuf::from(".")); path.push("craftlabs"); fs::create_dir_all(&path).ok(); path.join("config.json") } fn main() { let cli = Cli::parse(); let config_path = get_config_path(); let mut config = Config::load(&config_path); if let Some(api_url) = &cli.api { config.api_base_url = api_url.clone(); } let rt = tokio::runtime::Runtime::new().unwrap(); match &cli.command { Commands::Status => { print_status(&config, cli.json); } Commands::Activate { sn } => { let handle = init_engine(); if handle.is_null() { eprintln!("错误: 初始化授权引擎失败"); std::process::exit(1); } let c_sn = CString::new(sn.as_str()).unwrap(); let result = craft_activate(handle, c_sn.as_ptr(), std::ptr::null()); if result.success != 0 { println!("OK: 激活成功"); config.sn = Some(sn.clone()); config.save(&config_path); rt.block_on(sync_activation(&config, sn)); } else { let msg = if result.message.is_null() { "未知错误" } else { unsafe { std::ffi::CStr::from_ptr(result.message) }.to_str().unwrap_or("未知错误") }; eprintln!("错误: 激活失败 - {}", msg); } craft_destroy(handle); } Commands::Check => { let handle = init_engine(); if handle.is_null() { eprintln!("错误: 初始化失败"); return; } let result = craft_check_license(handle); if cli.json { let msg = if !result.message.is_null() { unsafe { std::ffi::CStr::from_ptr(result.message) }.to_str().unwrap_or("") } else { "" }; println!("{{\"status\":{},\"message\":\"{}\"}}", result.success, msg); } else { println!("授权状态: {}", if result.success != 0 { "有效" } else { "无效" }); } craft_destroy(handle); } Commands::Info => { let handle = init_engine(); if handle.is_null() { eprintln!("错误: 初始化失败"); return; } let info_ptr = craft_get_license_info(handle); if !info_ptr.is_null() { let info = unsafe { &*info_ptr }; println!("授权状态: {}", if info.is_licensed != 0 { "已授权" } else { "未授权" }); if info.expiration_date > 0 { println!("过期时间戳: {}", info.expiration_date); } if info.feature_count > 0 { println!("功能特性 ({}):", info.feature_count); let names = unsafe { std::slice::from_raw_parts(info.feature_names, info.feature_count as usize) }; let values = unsafe { std::slice::from_raw_parts(info.feature_values, info.feature_count as usize) }; for idx in 0..info.feature_count as usize { let name = if idx < names.len() && !names[idx].is_null() { unsafe { std::ffi::CStr::from_ptr(names[idx]) } .to_str().unwrap_or("?") } else { "?" }; let val = if idx < values.len() { values[idx] } else { 0 }; println!(" {}: {}", name, if val != 0 { "ON" } else { "OFF" }); } } craft_free_license_info(info_ptr); } else { println!("无法获取授权信息"); } craft_destroy(handle); } Commands::Release => { let handle = init_engine(); if handle.is_null() { eprintln!("错误: 初始化失败"); return; } let result = craft_release(handle); if result.success != 0 { println!("OK: 授权已撤销"); config.sn = None; config.save(&config_path); } else { eprintln!("错误: 撤销失败"); } craft_destroy(handle); } Commands::Migrate { sn } => { println!("正在迁移授权到新 SN: {} ...", sn); let handle = init_engine(); if handle.is_null() { eprintln!("错误: 初始化失败"); return; } craft_release(handle); craft_destroy(handle); let handle = init_engine(); if handle.is_null() { eprintln!("错误: 初始化失败"); return; } let c_sn = CString::new(sn.as_str()).unwrap(); let result = craft_activate(handle, c_sn.as_ptr(), std::ptr::null()); if result.success != 0 { println!("OK: 迁移成功"); config.sn = Some(sn.clone()); config.save(&config_path); } else { eprintln!("错误: 迁移失败"); } craft_destroy(handle); } Commands::DeviceId => { let fp = device::collect(); if cli.json { println!("{{\"device_id\":\"{}\"}}", fp.composite_hash); } else { println!("设备指纹: {}", fp.composite_hash); } } Commands::Heartbeat => { let handle = init_engine(); if handle.is_null() { eprintln!("错误: 初始化失败"); return; } let result = craft_heartbeat(handle); println!("心跳: {}", if result.success != 0 { "OK" } else { "FAIL" }); craft_destroy(handle); } Commands::Config { action } => { handle_config(action, &mut config, &config_path); } } } fn init_engine() -> *mut craftlabs_auth_core::CraftContext { let config_json = r#"{"provider":"selfhosted","schemaVersion":1,"scenario":"floating"}"#; let c = CString::new(config_json).unwrap(); craft_initialize(c.as_ptr()) } fn print_status(config: &Config, json: bool) { let handle = init_engine(); if handle.is_null() { eprintln!("错误: 初始化失败"); return; } let result = craft_check_license(handle); let fp = device::collect(); if json { println!("{{\"licensed\":{},\"device_id\":\"{}\",\"api\":\"{}\",\"sn\":{}}}", result.success, fp.composite_hash, config.api_base_url, config.sn.as_deref().map(|s| format!("\"{}\"", s)).unwrap_or("null".into())); } else { println!("授权状态: {}", if result.success != 0 { "有效" } else { "无效" }); println!("设备指纹: {}", fp.composite_hash); println!("API 地址: {}", config.api_base_url); if let Some(sn) = &config.sn { println!("绑定 SN: {}", sn); } } craft_destroy(handle); } fn handle_config(action: &[String], config: &mut Config, path: &PathBuf) { if action.is_empty() { println!("API 地址: {}", config.api_base_url); println!("绑定 SN: {}", config.sn.as_deref().unwrap_or("(无)")); println!("配置路径: {}", path.display()); return; } match action[0].as_str() { "set-api" if action.len() > 1 => { config.api_base_url = action[1].clone(); config.save(path); println!("OK: API 地址已更新"); } "set-sn" if action.len() > 1 => { config.sn = Some(action[1].clone()); config.save(path); println!("OK: SN 已设置"); } _ => eprintln!("用法: craft config [set-api |set-sn ]"), } } async fn sync_activation(config: &Config, sn: &str) { let client = PlatformClient::new(&config.api_base_url); match client.report_activation(sn, &device::collect().composite_hash).await { Ok(_) => println!("平台同步成功"), Err(e) => eprintln!("平台同步失败: {} (不影响本地授权)", e), } }