use std::path::PathBuf;
use anyhow::{Context, Result};
use color_print::cformat;
use worktrunk::path::format_path_for_display;
use worktrunk::styling::{eprintln, hint_message, info_message, success_message};
use crate::output::prompt::{PromptResponse, prompt_yes_no_preview};
const PLUGIN_SOURCE: &str = include_str!("../../../dev/opencode-plugin.ts");
fn opencode_plugins_dir() -> Result<PathBuf> {
let config_dir = if let Ok(dir) = std::env::var("OPENCODE_CONFIG_DIR") {
PathBuf::from(dir)
} else {
dirs::config_dir()
.context("Could not determine config directory")?
.join("opencode")
};
Ok(config_dir.join("plugins"))
}
pub fn plugin_path() -> Result<PathBuf> {
Ok(opencode_plugins_dir()?.join("worktrunk.ts"))
}
pub fn is_plugin_installed() -> bool {
plugin_path()
.ok()
.and_then(|p| std::fs::read_to_string(p).ok())
.is_some_and(|content| content == PLUGIN_SOURCE)
}
pub fn plugin_file_exists() -> bool {
plugin_path().map(|p| p.exists()).unwrap_or(false)
}
fn confirm_or_yes(yes: bool, prompt: &str, preview: impl Fn()) -> Result<bool> {
Ok(yes || prompt_yes_no_preview(prompt, preview)? == PromptResponse::Accepted)
}
pub fn handle_opencode_install(yes: bool) -> Result<()> {
let target = plugin_path()?;
let target_display = format_path_for_display(&target);
if target.exists()
&& let Ok(existing) = std::fs::read_to_string(&target)
&& existing == PLUGIN_SOURCE
{
eprintln!(
"{}",
info_message(cformat!(
"Plugin already installed @ <bold>{target_display}</>"
))
);
return Ok(());
}
let action = if target.exists() { "Update" } else { "Install" };
let preview_msg = info_message(cformat!("Would write to <bold>{target_display}</>"));
let preview = || eprintln!("{}", preview_msg);
let confirmed = confirm_or_yes(
yes,
&cformat!("{action} OpenCode plugin @ <bold>{target_display}</>?"),
preview,
);
if !confirmed? {
return Ok(());
}
let parent = target
.parent()
.context("Plugin path has no parent directory")?;
std::fs::create_dir_all(parent)
.with_context(|| format!("Failed to create directory {}", parent.display()))?;
std::fs::write(&target, PLUGIN_SOURCE)
.with_context(|| format!("Failed to write plugin to {target_display}"))?;
eprintln!(
"{}",
success_message(cformat!("Plugin installed @ <bold>{target_display}</>"))
);
eprintln!(
"{}",
hint_message(cformat!(
"Activity markers (🤖/💬) will appear in <underline>wt list</>"
))
);
Ok(())
}
pub fn handle_opencode_uninstall(yes: bool) -> Result<()> {
let target = plugin_path()?;
let target_display = format_path_for_display(&target);
if !target.exists() {
eprintln!("{}", info_message("Plugin not installed"));
return Ok(());
}
let preview_msg = info_message(cformat!("Would remove <bold>{target_display}</>"));
let preview = || eprintln!("{}", preview_msg);
let confirmed = confirm_or_yes(
yes,
&cformat!("Remove OpenCode plugin @ <bold>{target_display}</>?"),
preview,
);
if !confirmed? {
return Ok(());
}
std::fs::remove_file(&target)
.with_context(|| format!("Failed to remove plugin at {target_display}"))?;
eprintln!(
"{}",
success_message(cformat!("Plugin removed from <bold>{target_display}</>"))
);
Ok(())
}