fix: detect claude cli on windows

This commit is contained in:
lucaronin
2026-04-26 07:34:27 +02:00
parent 65a89f102a
commit e4083ded1a
3 changed files with 66 additions and 7 deletions

View File

@@ -635,7 +635,7 @@ Tolaria tracks managed vault-level AI guidance separately from normal note conte
`useAiAgentsOnboarding(enabled)` adds a separate first-launch agent step:
- Reads a local dismissal flag for the AI agents prompt (with a legacy fallback to the older Claude-only key)
- Only shows after vault onboarding has already resolved to a ready state
- Uses `get_ai_agents_status`, whose backend treats the app process path, login-shell path, and supported local/toolchain/app install locations as valid CLI-agent sources
- Uses `get_ai_agents_status`, whose backend treats the app process path, login-shell path, and supported local/toolchain/app install locations, including Windows `.exe` and npm/pnpm/Scoop shim paths, as valid CLI-agent sources
- Persists dismissal locally once the user continues
### Remote Git Operations

View File

@@ -217,7 +217,7 @@ Full agent mode — spawns the selected local CLI agent as a subprocess with too
3. **Agent adapters** — Claude Code still uses `claude_cli.rs`; Codex runs through `codex exec --json` with the CLI's normal approval / sandbox defaults
4. **MCP Integration** — Claude receives the generated MCP config file path, while Codex receives the same Tolaria MCP server via transient `-c mcp_servers.tolaria.*` config overrides
CLI-agent availability intentionally does not depend only on the desktop app's inherited `PATH`. The detectors check the current process path, the user's login shell, and supported local/toolchain install locations such as native `~/.local/bin`, local `~/.claude/local`, Mise/asdf shims, npm-global, Homebrew, and the macOS Codex app resource path so first-run onboarding works on fresh macOS installs.
CLI-agent availability intentionally does not depend only on the desktop app's inherited `PATH`. The detectors check the current process path, the user's login shell, and supported local/toolchain install locations such as native `~/.local/bin`, local `~/.claude/local`, Mise/asdf shims, npm-global, Homebrew, Windows `%APPDATA%\npm`/pnpm/Scoop shims, Windows `.exe` launchers, and the macOS Codex app resource path so first-run onboarding works on fresh macOS and Windows installs.
#### Agent Event Flow

View File

@@ -63,7 +63,7 @@ pub struct AgentStreamRequest {
// ---------------------------------------------------------------------------
pub(crate) fn find_claude_binary() -> Result<PathBuf, String> {
if let Some(binary) = find_claude_binary_on_path()? {
if let Some(binary) = find_claude_binary_on_path() {
return Ok(binary);
}
@@ -78,13 +78,20 @@ pub(crate) fn find_claude_binary() -> Result<PathBuf, String> {
Err("Claude CLI not found. Install it: https://docs.anthropic.com/en/docs/claude-code".into())
}
fn find_claude_binary_on_path() -> Result<Option<PathBuf>, String> {
let output = Command::new("which")
fn find_claude_binary_on_path() -> Option<PathBuf> {
Command::new(claude_path_lookup_command())
.arg("claude")
.output()
.map_err(|e| format!("Failed to run `which claude`: {e}"))?;
.ok()
.and_then(|output| path_from_successful_output(&output))
}
Ok(path_from_successful_output(&output))
fn claude_path_lookup_command() -> &'static str {
if cfg!(windows) {
"where"
} else {
"which"
}
}
fn find_claude_binary_in_user_shell() -> Option<PathBuf> {
@@ -143,11 +150,24 @@ fn claude_binary_candidates() -> Vec<PathBuf> {
fn claude_binary_candidates_for_home(home: &Path) -> Vec<PathBuf> {
vec![
home.join(".local/bin/claude"),
home.join(".local/bin/claude.exe"),
home.join(".claude/local/claude"),
home.join(".claude/local/claude.exe"),
home.join(".local/share/mise/shims/claude"),
home.join(".local/share/mise/shims/claude.exe"),
home.join(".asdf/shims/claude"),
home.join(".asdf/shims/claude.exe"),
home.join(".npm-global/bin/claude"),
home.join(".npm-global/bin/claude.cmd"),
home.join(".npm-global/bin/claude.exe"),
home.join(".npm/bin/claude"),
home.join(".npm/bin/claude.cmd"),
home.join(".npm/bin/claude.exe"),
home.join("AppData/Roaming/npm/claude.cmd"),
home.join("AppData/Roaming/npm/claude.exe"),
home.join("AppData/Local/pnpm/claude.cmd"),
home.join("AppData/Local/pnpm/claude.exe"),
home.join("scoop/shims/claude.exe"),
PathBuf::from("/opt/homebrew/bin/claude"),
PathBuf::from("/usr/local/bin/claude"),
]
@@ -1099,6 +1119,45 @@ mod tests {
}
}
#[test]
fn claude_binary_candidates_include_windows_exe_installs() {
let home = PathBuf::from(r"C:\Users\alex");
let candidates = claude_binary_candidates_for_home(&home);
let expected = [
home.join(".local/bin/claude.exe"),
home.join(".claude/local/claude.exe"),
home.join("AppData/Roaming/npm/claude.cmd"),
];
for candidate in expected {
assert!(
candidates.contains(&candidate),
"missing {}",
candidate.display()
);
}
}
#[test]
fn claude_path_lookup_command_matches_current_platform() {
let expected = if cfg!(windows) { "where" } else { "which" };
assert_eq!(claude_path_lookup_command(), expected);
}
#[test]
fn find_existing_binary_finds_windows_exe_candidate() {
let dir = tempfile::tempdir().unwrap();
let claude = dir.path().join(".local/bin/claude.exe");
std::fs::create_dir_all(claude.parent().unwrap()).unwrap();
std::fs::write(&claude, "").unwrap();
assert_eq!(
find_existing_binary(claude_binary_candidates_for_home(dir.path())),
Some(claude)
);
}
#[test]
fn find_claude_binary_returns_result() {
let result = find_claude_binary();