use crate::cacher::Cacher;
use crate::crates::CratesApi;
use crate::mcp::McpApi;
use crate::opts::{AppCommand, Opts, UpdateCommand};
use crate::workbench::WorkbenchApi;
use anyhow::{Result, bail};
use dialoguer::Confirm;
use owo_colors::OwoColorize;
use std::fs;
use std::process::Stdio;
use tokio::process::Command;
pub struct App {
opts: Opts,
cacher: Cacher,
crates_api: CratesApi,
mcp_api: McpApi,
workbench_api: WorkbenchApi,
}
impl App {
pub async fn entrypoint(opts: Opts) -> Result<()> {
let mut app = Self::init(opts).await?;
match &app.opts.command {
None => {
app.update_workbench(false, false).await?;
app.command_workbench().await?;
}
Some(AppCommand::Update(opts)) => {
let opts = Some(opts.clone());
app.command_update(true, false, opts).await?;
}
Some(AppCommand::Upgrade(opts)) => {
let opts = Some(opts.clone());
app.command_update(true, true, opts).await?;
}
Some(AppCommand::Workbench) => {
app.update_workbench(false, false).await?;
app.command_workbench().await?;
}
Some(AppCommand::Mcp { args }) => {
let player_args = args.clone();
app.update_mcp(false, false).await?;
app.command_mcp(&player_args).await?;
}
Some(AppCommand::Clean) => {
app.command_clean().await?;
}
}
Ok(())
}
async fn init(opts: Opts) -> Result<Self> {
let mut cacher = Cacher::create().await?;
cacher.initialize().await?;
Ok(Self {
opts,
cacher,
crates_api: CratesApi::new(),
mcp_api: McpApi::new(),
workbench_api: WorkbenchApi::new(),
})
}
async fn update_launcher(&mut self, force_check: bool) -> Result<()> {
if self.cacher.launcher.is_update_required() || force_check {
eprintln!("Checking an update for the launcher...");
let version = self.crates_api.latest_version().await?;
if self.cacher.launcher.is_outdated(version) {
eprintln!(
"New version of launcher is available. To update run the following command"
);
eprintln!("{}", "cargo install knowledge".green());
}
self.cacher.launcher.update_check();
self.cacher.write_state().await?;
}
Ok(())
}
async fn update_mcp(&mut self, force_check: bool, force_download: bool) -> Result<()> {
if self.cacher.player.is_update_required() || force_check {
eprintln!("Checking for mcp updates...");
let latest = self.mcp_api.latest_release().await?;
let version = latest.version.clone();
if self.cacher.player.is_outdated(version.clone()) || force_download {
eprintln!("Downloading mcp {}...", version);
let url = latest.get_asset_url();
let bin_name = if cfg!(target_os = "windows") {
"mcp.exe"
} else {
"mcp"
};
let dest = self.cacher.bin_dir().join(bin_name);
self.mcp_api.download_binary(&url, &dest).await?;
#[cfg(not(target_os = "windows"))]
{
use std::os::unix::fs::PermissionsExt;
let mut perm = fs::metadata(&dest)?.permissions();
perm.set_mode(perm.mode() | 0o755);
fs::set_permissions(&dest, perm)?;
}
self.cacher.player.version = Some(version.clone());
eprintln!("mcp {} is ready.", version);
}
self.cacher.player.update_check();
self.cacher.write_state().await?;
}
Ok(())
}
async fn update_workbench(&mut self, force_check: bool, force_download: bool) -> Result<()> {
if self.cacher.workbench.is_update_required() || force_check {
eprintln!("Checking for workbench updates...");
let latest = self.workbench_api.latest_release().await?;
let version = latest.version.clone();
if self.cacher.workbench.is_outdated(version.clone()) || force_download {
eprintln!("Downloading workbench {}...", version);
let url = latest.get_asset_url();
let bin_name = if cfg!(target_os = "windows") {
"workbench.exe"
} else {
"workbench"
};
let dest = self.cacher.bin_dir().join(bin_name);
self.workbench_api.download_binary(&url, &dest).await?;
#[cfg(not(target_os = "windows"))]
{
use std::os::unix::fs::PermissionsExt;
let mut perm = fs::metadata(&dest)?.permissions();
perm.set_mode(perm.mode() | 0o755);
fs::set_permissions(&dest, perm)?;
}
self.cacher.workbench.version = Some(version.clone());
eprintln!("workbench {} is ready.", version);
}
self.cacher.workbench.update_check();
self.cacher.write_state().await?;
}
Ok(())
}
pub async fn command_update(
&mut self,
force_check: bool,
force_download: bool,
_opts: Option<UpdateCommand>,
) -> Result<()> {
if let Err(err) = self.update_launcher(force_check).await {
eprintln!("Launcher updating failed: {}", err.to_string().red());
}
if let Err(err) = self.update_mcp(force_check, force_download).await {
eprintln!("MCP updating failed: {}", err.to_string().red());
}
if let Err(err) = self.update_workbench(force_check, force_download).await {
eprintln!("Workbench updating failed: {}", err.to_string().red());
}
Ok(())
}
pub async fn command_mcp(&mut self, player_args: &[String]) -> Result<()> {
let mut bin_path = self.cacher.bin_dir().clone();
let bin_name = if cfg!(target_os = "windows") {
"mcp.exe"
} else {
"mcp"
};
bin_path.push(bin_name);
if !bin_path.exists() {
bail!("MCP binary not found. Run 'knowledge update' first.");
}
eprintln!("Starting mcp...");
let mut command = Command::new(&bin_path);
command
.args(player_args)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
let status = command.status().await?;
if !status.success() {
bail!("mcp exited with error");
}
Ok(())
}
pub async fn command_workbench(&mut self) -> Result<()> {
let mut bin_path = self.cacher.bin_dir().clone();
let bin_name = if cfg!(target_os = "windows") {
"workbench.exe"
} else {
"workbench"
};
bin_path.push(bin_name);
if !bin_path.exists() {
bail!("Workbench binary not found. Run 'knowledge update' first.");
}
eprintln!("Starting workbench...");
let mut command = Command::new(&bin_path);
command
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
let status = command.status().await?;
if !status.success() {
bail!("workbench exited with error");
}
Ok(())
}
pub async fn command_clean(self) -> Result<()> {
if Confirm::new()
.with_prompt("Do you want to clean the cache?")
.interact()?
{
eprintln!("Cleaning the cache...");
self.cacher.remove_cache().await?;
}
Ok(())
}
}