Skip to content
Merged
264 changes: 212 additions & 52 deletions src-tauri/src/commands/config.rs

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,10 @@ fn build_enhanced_path() -> String {
if let Ok(prefix) = std::env::var("NPM_CONFIG_PREFIX") {
extra.push(format!("{}/bin", prefix));
}
// standalone 安装目录(集中管理,避免多处硬编码)
for sa_dir in config::all_standalone_dirs() {
extra.push(sa_dir.to_string_lossy().into_owned());
}
// 扫描 nvm 实际安装的版本目录(兼容无 current 符号链接的情况)
// 按版本号倒序排列,确保最新版优先(修复 #143:v20 排在 v24 前面)
let nvm_versions = home.join(".nvm/versions/node");
Expand Down Expand Up @@ -326,6 +330,10 @@ fn build_enhanced_path() -> String {
if let Ok(prefix) = std::env::var("NPM_CONFIG_PREFIX") {
extra.push(format!("{}/bin", prefix));
}
// standalone 安装目录(集中管理,避免多处硬编码)
for sa_dir in config::all_standalone_dirs() {
extra.push(sa_dir.to_string_lossy().into_owned());
}
// NVM_DIR 环境变量(用户可能自定义了 nvm 安装目录)
// 按版本号倒序排列,确保最新版优先(修复 #143:v20 排在 v24 前面)
let nvm_dir = std::env::var("NVM_DIR")
Expand Down Expand Up @@ -539,6 +547,14 @@ fn build_enhanced_path() -> String {
extra.push(format!(r"{}\npm", appdata));
}

// 6.5 standalone 安装目录(集中管理,避免多处硬编码)
// standalone 安装后通过注册表写入用户 PATH,但当前进程的 PATH 环境变量不会
// 实时更新,需要显式添加到 enhanced_path 以确保 resolve_openclaw_cli_path()
// 能找到 standalone 安装的 openclaw.cmd
for sa_dir in config::all_standalone_dirs() {
extra.push(sa_dir.to_string_lossy().into_owned());
}

// 7. 系统默认 Node.js 安装路径(优先级最低)
extra.push(format!(r"{}\nodejs", pf));
extra.push(format!(r"{}\nodejs", pf86));
Expand Down
31 changes: 11 additions & 20 deletions src-tauri/src/commands/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,11 @@ mod platform {

fn common_cli_candidates() -> Vec<PathBuf> {
let mut candidates = Vec::new();
if let Some(home) = dirs::home_dir() {
candidates.push(home.join(".openclaw-bin").join("openclaw"));
// standalone 安装目录(集中管理,避免多处硬编码)
for sa_dir in crate::commands::config::all_standalone_dirs() {
candidates.push(sa_dir.join("openclaw"));
}
candidates.push(PathBuf::from("/opt/openclaw/openclaw"));
// Homebrew 路径(非 standalone,保留)
candidates.push(PathBuf::from("/opt/homebrew/bin/openclaw"));
candidates.push(PathBuf::from("/usr/local/bin/openclaw"));
candidates
Expand Down Expand Up @@ -849,23 +850,9 @@ mod platform {
fn candidate_cli_paths() -> Vec<PathBuf> {
let mut candidates = Vec::new();

// standalone 安装目录(优先检测,覆盖所有可能位置)
if let Ok(localappdata) = env::var("LOCALAPPDATA") {
// Inno Setup PrivilegesRequired=lowest 默认路径
candidates.push(
Path::new(&localappdata)
.join("Programs")
.join("OpenClaw")
.join("openclaw.cmd"),
);
candidates.push(
Path::new(&localappdata)
.join("OpenClaw")
.join("openclaw.cmd"),
);
}
if let Ok(pf) = env::var("ProgramFiles") {
candidates.push(Path::new(&pf).join("OpenClaw").join("openclaw.cmd"));
// standalone 安装目录(集中管理,避免多处硬编码)
for sa_dir in crate::commands::config::all_standalone_dirs() {
candidates.push(sa_dir.join("openclaw.cmd"));
}

if let Ok(appdata) = env::var("APPDATA") {
Expand Down Expand Up @@ -1224,6 +1211,10 @@ mod platform {
.join("openclaw"),
);
}
// standalone 安装目录(集中管理,避免多处硬编码)
for sa_dir in crate::commands::config::all_standalone_dirs() {
candidates.push(sa_dir.join("openclaw"));
}
candidates.push(PathBuf::from("/usr/local/bin/openclaw"));
candidates.push(PathBuf::from("/usr/bin/openclaw"));
for segment in crate::commands::enhanced_path().split(':') {
Expand Down
7 changes: 5 additions & 2 deletions src-tauri/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@ fn find_openclaw_cmd() -> Option<std::path::PathBuf> {
#[cfg(not(target_os = "windows"))]
fn common_non_windows_cli_candidates() -> Vec<std::path::PathBuf> {
let mut candidates = Vec::new();
// standalone 安装目录(集中管理,避免多处硬编码)
for sa_dir in crate::commands::config::all_standalone_dirs() {
candidates.push(sa_dir.join("openclaw"));
}
// 其他标准路径
if let Some(home) = dirs::home_dir() {
candidates.push(home.join(".openclaw-bin").join("openclaw"));
candidates.push(home.join(".local").join("bin").join("openclaw"));
}
candidates.push(std::path::PathBuf::from("/opt/openclaw/openclaw"));
candidates.push(std::path::PathBuf::from("/opt/homebrew/bin/openclaw"));
candidates.push(std::path::PathBuf::from("/usr/local/bin/openclaw"));
candidates.push(std::path::PathBuf::from("/usr/bin/openclaw"));
Expand Down
1 change: 1 addition & 0 deletions src/locales/modules/about.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/locales/modules/dashboard.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions src/pages/about.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ async function loadData(page) {
checkHotUpdate(cards, panelVersion)

const isInstalled = !!version.current
const sourceLabel = version.source === 'official' ? t('about.official') : t('about.chinese')
const sourceLabel = version.source === 'official' ? t('about.official') : version.source === 'chinese' ? t('about.chinese') : t('about.unknownSource')
const btnSm = 'padding:2px 8px;font-size:var(--font-size-xs)'
const hasRecommended = !!version.recommended
const aheadOfRecommended = isInstalled && hasRecommended && !!version.ahead_of_recommended
Expand Down Expand Up @@ -250,18 +250,18 @@ async function showVersionPicker(page, currentVersion) {
if (!targetVer || targetVer === '') { hintEl.textContent = ''; confirmBtn.disabled = true; return }
const targetTag = select.selectedIndex === 0 ? t('about.tagRecommended') : t('about.tagNeedTest')

const sameSource = targetSource === (currentVersion.source === 'official' ? 'official' : 'chinese')
const sameSource = targetSource === currentVersion.source

if (!isInstalled) {
confirmBtn.textContent = t('about.btnInstall')
hintEl.textContent = t('about.hintInstall', { source: targetSource === 'official' ? t('about.official') : t('about.chinese'), ver: targetVer, tag: targetTag })
hintEl.textContent = t('about.hintInstall', { source: targetSource === 'official' ? t('about.official') : targetSource === 'chinese' ? t('about.chinese') : t('about.unknownSource'), ver: targetVer, tag: targetTag })
confirmBtn.disabled = false
return
}

if (!sameSource) {
confirmBtn.textContent = t('about.btnSwitch')
hintEl.innerHTML = `${t('about.hintCurrent')}: <strong>${currentVersion.source === 'official' ? t('about.official') : t('about.chinese')} ${currentVersion.current}</strong> → <strong>${targetSource === 'official' ? t('about.official') : t('about.chinese')} ${targetVer}</strong>${targetTag}`
hintEl.innerHTML = `${t('about.hintCurrent')}: <strong>${currentVersion.source === 'official' ? t('about.official') : currentVersion.source === 'chinese' ? t('about.chinese') : t('about.unknownSource')} ${currentVersion.current}</strong> → <strong>${targetSource === 'official' ? t('about.official') : targetSource === 'chinese' ? t('about.chinese') : t('about.unknownSource')} ${targetVer}</strong>${targetTag}`
confirmBtn.disabled = false
return
}
Expand Down Expand Up @@ -310,7 +310,7 @@ async function showVersionPicker(page, currentVersion) {
const versions = showNightly ? allVersions : (stable.length > 0 ? stable : allVersions)
const nightlyCount = allVersions.length - stable.length
select.innerHTML = versions.map((v, idx) => {
const isCurrent = isInstalled && v === currentVersion.current && source === (currentVersion.source === 'official' ? 'official' : 'chinese')
const isCurrent = isInstalled && v === currentVersion.current && source === currentVersion.source
return `<option value="${v}">${v}${idx === 0 ? ` (${t('about.recommended')})` : ''}${isCurrent ? ` (${t('about.current')})` : ''}</option>`
}).join('')
// nightly 切换提示
Expand Down
2 changes: 1 addition & 1 deletion src/pages/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ function renderStatCards(page, services, version, agents, config) {
</div>
<div class="stat-card">
<div class="stat-card-header">
<span class="stat-card-label">${t('dashboard.versionLabel')} · ${version.source === 'official' ? t('dashboard.versionOfficial') : t('dashboard.versionChinese')}</span>
<span class="stat-card-label">${t('dashboard.versionLabel')} · ${version.source === 'official' ? t('dashboard.versionOfficial') : version.source === 'chinese' ? t('dashboard.versionChinese') : t('dashboard.versionUnknownSource')}</span>
</div>
<div class="stat-card-value">${version.current || t('common.unknown')}</div>
<div class="stat-card-meta">${versionMeta}</div>
Expand Down