cli.rs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. use tauri::Manager;
  2. const CLI_INSTALL_DIR: &str = ".opencode/bin";
  3. const CLI_BINARY_NAME: &str = "opencode";
  4. fn get_cli_install_path() -> Option<std::path::PathBuf> {
  5. std::env::var("HOME").ok().map(|home| {
  6. std::path::PathBuf::from(home)
  7. .join(CLI_INSTALL_DIR)
  8. .join(CLI_BINARY_NAME)
  9. })
  10. }
  11. pub fn get_sidecar_path(app: &tauri::AppHandle) -> std::path::PathBuf {
  12. // Get binary with symlinks support
  13. tauri::process::current_binary(&app.env())
  14. .expect("Failed to get current binary")
  15. .parent()
  16. .expect("Failed to get parent dir")
  17. .join("opencode-cli")
  18. }
  19. fn is_cli_installed() -> bool {
  20. get_cli_install_path()
  21. .map(|path| path.exists())
  22. .unwrap_or(false)
  23. }
  24. const INSTALL_SCRIPT: &str = include_str!("../../../../install");
  25. #[tauri::command]
  26. pub fn install_cli(app: tauri::AppHandle) -> Result<String, String> {
  27. if cfg!(not(unix)) {
  28. return Err("CLI installation is only supported on macOS & Linux".to_string());
  29. }
  30. let sidecar = get_sidecar_path(&app);
  31. if !sidecar.exists() {
  32. return Err("Sidecar binary not found".to_string());
  33. }
  34. let temp_script = std::env::temp_dir().join("opencode-install.sh");
  35. std::fs::write(&temp_script, INSTALL_SCRIPT)
  36. .map_err(|e| format!("Failed to write install script: {}", e))?;
  37. #[cfg(unix)]
  38. {
  39. use std::os::unix::fs::PermissionsExt;
  40. std::fs::set_permissions(&temp_script, std::fs::Permissions::from_mode(0o755))
  41. .map_err(|e| format!("Failed to set script permissions: {}", e))?;
  42. }
  43. let output = std::process::Command::new(&temp_script)
  44. .arg("--binary")
  45. .arg(&sidecar)
  46. .output()
  47. .map_err(|e| format!("Failed to run install script: {}", e))?;
  48. let _ = std::fs::remove_file(&temp_script);
  49. if !output.status.success() {
  50. let stderr = String::from_utf8_lossy(&output.stderr);
  51. return Err(format!("Install script failed: {}", stderr));
  52. }
  53. let install_path =
  54. get_cli_install_path().ok_or_else(|| "Could not determine install path".to_string())?;
  55. Ok(install_path.to_string_lossy().to_string())
  56. }
  57. pub fn sync_cli(app: tauri::AppHandle) -> Result<(), String> {
  58. if cfg!(debug_assertions) {
  59. println!("Skipping CLI sync for debug build");
  60. return Ok(());
  61. }
  62. if !is_cli_installed() {
  63. println!("No CLI installation found, skipping sync");
  64. return Ok(());
  65. }
  66. let cli_path =
  67. get_cli_install_path().ok_or_else(|| "Could not determine CLI install path".to_string())?;
  68. let output = std::process::Command::new(&cli_path)
  69. .arg("--version")
  70. .output()
  71. .map_err(|e| format!("Failed to get CLI version: {}", e))?;
  72. if !output.status.success() {
  73. return Err("Failed to get CLI version".to_string());
  74. }
  75. let cli_version_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
  76. let cli_version = semver::Version::parse(&cli_version_str)
  77. .map_err(|e| format!("Failed to parse CLI version '{}': {}", cli_version_str, e))?;
  78. let app_version = app.package_info().version.clone();
  79. if cli_version >= app_version {
  80. println!(
  81. "CLI version {} is up to date (app version: {}), skipping sync",
  82. cli_version, app_version
  83. );
  84. return Ok(());
  85. }
  86. println!(
  87. "CLI version {} is older than app version {}, syncing",
  88. cli_version, app_version
  89. );
  90. install_cli(app)?;
  91. println!("Synced installed CLI");
  92. Ok(())
  93. }