Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to the Toolpath workspace are documented here.

## Resume: cursor picker requires the `cursor` CLI — 2026-07-02

- **`path-cli`**: `path resume`'s harness picker no longer lists **cursor**
unless the `cursor` CLI is actually on `PATH`. `harness_available` previously
counted the always-present macOS `open` (the `open -a Cursor` fallback), so
cursor showed up even when Cursor wasn't installed. Now every harness gates
purely on its own binary being on `PATH`.

## Derive: resolve duplicate step ids — 2026-07-01

- **`toolpath-convo`** (0.11.1): `derive_path` now guarantees the derived
Expand Down
43 changes: 22 additions & 21 deletions crates/path-cli/src/cmd_resume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,21 +305,7 @@ pub(crate) fn harness_available(
harness: crate::cmd_share::Harness,
path_override: Option<&std::path::Path>,
) -> bool {
use crate::cmd_share::Harness;
if binary_on_path(harness.name(), path_override) {
return true;
}
if harness == Harness::Cursor {
#[cfg(target_os = "macos")]
{
return binary_on_path("open", path_override);
}
#[cfg(all(unix, not(target_os = "macos")))]
{
return binary_on_path("xdg-open", path_override);
}
}
false
binary_on_path(harness.name(), path_override)
}

const ALL_HARNESSES: &[crate::cmd_share::Harness] = &[
Expand Down Expand Up @@ -926,13 +912,28 @@ mod tests {
assert!(err.to_string().contains("`gemini` isn't on PATH"));
}

#[cfg(target_os = "macos")]
#[test]
fn cursor_available_via_open_fallback_on_macos() {
let td = fake_path_with(&["open"]);
assert!(harness_available(Harness::Cursor, Some(td.path())));
let picked = pick_harness(Some(HarnessArg::Cursor), None, Some(td.path()));
assert_eq!(picked.unwrap(), Harness::Cursor);
fn cursor_requires_its_cli_not_just_open() {
// Regression: the always-present macOS `open` must NOT make cursor
// look installed — it needs the real `cursor` CLI.
let only_open = fake_path_with(&["open"]);
assert!(
!harness_available(Harness::Cursor, Some(only_open.path())),
"cursor must not appear just because `open` exists"
);
assert!(
pick_harness(Some(HarnessArg::Cursor), None, Some(only_open.path()))
.unwrap_err()
.to_string()
.contains("isn't on PATH")
);

let with_cursor = fake_path_with(&["cursor"]);
assert!(harness_available(Harness::Cursor, Some(with_cursor.path())));
assert_eq!(
pick_harness(Some(HarnessArg::Cursor), None, Some(with_cursor.path())).unwrap(),
Harness::Cursor
);
}

#[test]
Expand Down
Loading