use super::dto::DesktopSessionItem;
use crate::daemon::LifecycleReadOptions;
use crate::lifecycle_store::{LifecycleStore, lifecycle_root_from_config};
use crate::session_sources::{load_provider_sessions, raw_session_id};
use std::fs;
use std::path::Path;
use super::dto::DesktopDaemonRequest;
pub fn parse_file_list(value: &str) -> Vec<String> {
value
.split([',', '\n'])
.map(str::trim)
.filter(|item| !item.is_empty())
.map(ToString::to_string)
.collect()
}
pub fn parse_csv_items(value: &str) -> Vec<String> {
value
.split(',')
.map(str::trim)
.filter(|item| !item.is_empty())
.map(ToString::to_string)
.collect()
}
pub(super) fn lifecycle_read_options(
daemon: Option<&DesktopDaemonRequest>,
) -> LifecycleReadOptions {
match daemon {
Some(DesktopDaemonRequest {
enabled: true,
daemon_bin: Some(daemon_bin),
}) => LifecycleReadOptions::with_daemon(daemon_bin.as_path()),
_ => LifecycleReadOptions::default(),
}
}
pub(super) fn store_from_config_path(config_path: &Path) -> LifecycleStore {
let config_dir = config_path.parent().unwrap_or_else(|| Path::new("."));
LifecycleStore::new(lifecycle_root_from_config(config_dir).as_path())
}
pub(super) fn load_session_index(_config_path: &Path) -> anyhow::Result<Vec<DesktopSessionItem>> {
let mut sessions = load_provider_sessions(None)?;
sessions.sort_by(|left, right| right.updated_at.cmp(&left.updated_at));
Ok(sessions)
}
pub(super) fn load_session_index_filtered(
_config_path: &Path,
provider_filter: Option<&str>,
) -> anyhow::Result<Vec<DesktopSessionItem>> {
let mut sessions = load_provider_sessions(provider_filter)?;
sessions.sort_by(|left, right| right.updated_at.cmp(&left.updated_at));
Ok(sessions)
}
pub(super) fn build_continue_command(session: &DesktopSessionItem) -> Option<String> {
let cwd_prefix = session
.cwd
.as_deref()
.map(shell_cd_prefix)
.unwrap_or_default();
let raw_id = raw_session_id(&session.session_id);
match session.provider.as_str() {
"claude" => Some(format!("{cwd_prefix}claude -r \"{raw_id}\"")),
"codex" => Some(format!("{cwd_prefix}codex resume \"{raw_id}\"")),
_ => None,
}
}
pub(super) fn delete_session_file(session: &DesktopSessionItem) -> anyhow::Result<()> {
let Some(path) = session.source_path.as_deref() else {
anyhow::bail!("当前会话没有可删除的本地文件")
};
let file_path = Path::new(path);
if !file_path.exists() {
anyhow::bail!("本地会话文件不存在:{}", file_path.display())
}
if !file_path.is_file() {
anyhow::bail!("本地会话路径不是文件:{}", file_path.display())
}
fs::remove_file(file_path)?;
Ok(())
}
fn shell_cd_prefix(cwd: &str) -> String {
format!("cd '{}' && ", cwd.replace('\'', "'\\''"))
}
pub(super) fn launch_terminal_command(command: &str) -> anyhow::Result<()> {
#[cfg(target_os = "macos")]
{
let script = format!(
"tell application \"Terminal\"\nactivate\ndo script \"{}\"\nend tell",
command.replace('\\', "\\\\").replace('"', "\\\"")
);
let output = std::process::Command::new("osascript")
.arg("-e")
.arg(script)
.output()?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
let detail = if stderr.is_empty() {
format!("osascript exited with status {}", output.status)
} else {
stderr
};
anyhow::bail!("无法通过 Terminal 启动命令 `{command}`:{detail}");
}
Ok(())
}
#[cfg(not(target_os = "macos"))]
{
let _ = command;
anyhow::bail!("continue session is only implemented for macOS right now")
}
}