Commit graph

52 commits

Author SHA1 Message Date
Lahfir
11f8da06e8 feat: add fallback chains for set-value, clear, focus, scroll-to, type and post-action state hints
Stage B of the centralized chain executor plan:
- set-value chain: direct AXSetValue → focus+AXSetValue
- clear chain: AXSetValue("") → focus+AXSetValue("") → Cmd+A+Delete
- focus chain: AXFocused → AXRaise → AXPress → AXSelected → CGClick
- scroll-to chain: AXScrollToVisible → walk-parents-scroll
- type: app-focus before typing, non-ASCII via clipboard paste (Cmd+V)
- post-action state hints: read element role/value/states after stateful
  actions (click, toggle, check, set-value, clear, type, expand, collapse)
- FocusThenSetDynamic chain step variant for focus-before-set patterns
- export copy_value_typed for reading numeric AXValues (checkboxes)
2026-02-23 04:41:12 -08:00
Lahfir
2a52d62106 fix: add dwell time before drag release for drop target recognition 2026-02-23 04:24:35 -08:00
Lahfir
e154cc0cc0 refactor: address code review findings
- dispatch.rs: 460 → 392 LOC by removing duplicate command_name(),
  using cmd.name() from cli.rs, and moving cli_surface_to_core to
  Surface::to_core() in cli_args.rs
- ElementCaps: trim from 8 to 3 used fields, remove has_action() and
  unused discovery calls (actions, role, has_children, pid, settable_value)
- chain.rs: deduplicate ChainStep/ChainDef/ChainContext out of cfg gates
- ax_helpers.rs: delete dead set_ax_string
2026-02-23 04:10:50 -08:00
Lahfir
c7316e8b51 feat: add structured verbose logging across all layers
Adds tracing::debug! calls throughout command and adapter layers,
activated via -v flag. Logs command dispatch, ref resolution, chain
step execution, clipboard/keyboard/mouse synthesis, and system ops.

Command layer logging (free for all platforms):
- dispatch: command name
- resolve: ref lookup with pid/role/name, match result
- tree: snapshot app/window/ref_count

Adapter layer logging (macOS, other platforms add their own):
- chain: step-by-step [N/total] with success/skip for each
- action: perform entry
- clipboard/keyboard/mouse: operation details
- system: app focus/launch/close, window ops, screenshots
2026-02-23 03:52:57 -08:00
Lahfir
4fe91e3a96 refactor: centralize AX chain executor with error suggestions and resilience
- Add ax_helpers.rs: 16 shared AX utility functions, eliminating ~150 LOC duplication
- Add chain.rs: declarative ChainStep executor with 9 step variants and 10s deadline
- Add chain_defs.rs: static chain definitions for click (14-step), right-click, expand, collapse
- Add discovery.rs: one-time ElementCaps query shared across chain steps
- Replace NSPasteboard subprocess calls with direct objc_msgSend FFI (zero new deps)
- Add .with_suggestion() to 16 high-priority error paths across 8 files
- Fix FocusThenAction to use retried variant for kAXErrorCannotComplete resilience
- Add relaxed stale ref matching (name-only fallback when bounds change)
- Delete activate.rs, absorb logic into chain_defs and ax_helpers (-388 net LOC)
2026-02-23 03:32:17 -08:00
Lahfir
ada8f4f0ee chore: remove completed todo files and build artifacts 2026-02-23 02:04:54 -08:00
Lahfir
ef45135087 feat: add agent-desktop skill for universal AI agent support
Installable via: npx skills add lahfir/agent-desktop
2026-02-23 02:02:00 -08:00
Lahfir
396e3e5abf revert: remove .claude directory from git tracking
Keep .claude/ fully ignored — skills are distributed via the
.skill package on GitHub Releases, not checked into the repo.
2026-02-23 01:52:25 -08:00
Lahfir
ad91cd32cf feat: add Claude Code skills for agent-desktop automation
Graph of skills covering all 50 commands with platform-specific sub-skills:
- Main index skill with core concepts, ref system, JSON contract
- Observation, interaction, and system command references
- Common workflow patterns (forms, menus, dialogs, drag-drop)
- macOS platform skill (permissions, AX API, troubleshooting)
- /desktop slash command for quick reference
- PRD addendum for skill maintenance across phases
2026-02-23 01:51:31 -08:00
Lahfir
572a2a3f25
Merge pull request #6 from lahfir/release-please--branches--main--components--agent-desktop
chore(main): release 0.1.3
2026-02-23 01:25:59 -08:00
github-actions[bot]
9932e352b7
chore(main): release 0.1.3 2026-02-23 09:25:38 +00:00
Lahfir
ebafb71603 perf: use curl for binary download in postinstall
Replace Node.js https.get with curl for 15x faster downloads.
GitHub's redirect chain causes excessive TLS handshake overhead
in Node's http client.
2026-02-23 01:25:22 -08:00
Lahfir
8f66a9346e fix: correct GitHub Release download URL and simplify tag format
Use v{version} tags instead of agent-desktop-v{version} by setting
include-component-in-tag: false in release-please config.
2026-02-23 01:20:16 -08:00
Lahfir
084fc8c960 fix: include README and CHANGELOG in npm package 2026-02-23 01:16:15 -08:00
Lahfir
8cbca19504
Merge pull request #5 from lahfir/release-please--branches--main--components--agent-desktop
chore(main): release agent-desktop 0.1.2
2026-02-23 01:04:16 -08:00
github-actions[bot]
e58c0732c8
chore(main): release agent-desktop 0.1.2 2026-02-23 09:03:37 +00:00
Lahfir
91c76777cb fix: use macos-latest for both build targets
macos-13 Intel runners are no longer available. Cross-compile x86_64
on ARM runners instead, which Rust handles natively via --target.
2026-02-23 01:03:24 -08:00
Lahfir
dd5532c454
Merge pull request #4 from lahfir/release-please--branches--main--components--agent-desktop
chore(main): release agent-desktop 0.1.1
2026-02-23 01:00:06 -08:00
github-actions[bot]
dc9f2afddf
chore(main): release agent-desktop 0.1.1 2026-02-23 08:59:15 +00:00
Lahfir
0ab78dde0e fix: use simple release strategy for workspace version bumps
cargo-workspace plugin cannot parse version.workspace = true in member
crates. Switch to simple strategy with generic file updater using
x-release-please-version annotation in root Cargo.toml.
2026-02-23 00:57:28 -08:00
Lahfir
18fc50cca5
feat: add release automation with GitHub Releases and npm distribution
Automated release pipeline using release-please for gated Release PRs,
conventional commits for SemVer version bumps, and npm distribution
with postinstall binary download from GitHub Releases.
2026-02-23 00:55:45 -08:00
Lahfir
aa3b210708 style: fix cargo fmt check in activate.rs 2026-02-21 23:37:02 -08:00
Lahfir
48f8470948 feat: add structural hints to splitter columns in snapshots
Multi-column apps like Notes, Mail, and Finder bury the editor
textfield deep in the tree (e.g. ref 256 of 272). Agents picking
"the first textfield" grab the wrong one. Column hints let agents
distinguish sidebar vs content-list vs editor panes.
2026-02-21 17:28:49 -08:00
Lahfir
a349bd9bc0 docs: rewrite README with AX-first interaction chains and inline menu capture 2026-02-21 15:41:28 -08:00
Lahfir
cddc5d3547 feat: AX-first right-click chain with inline context menu capture
- Add 7-step right-click chain: AXShowMenu direct, focus-app +
  AXShowMenu, select + AXShowMenu, focus-element + AXShowMenu,
  parent AXShowMenu, child AXShowMenu, CGEvent last resort
- Key fix: AXShowMenu returns -25204 (CannotComplete) when app
  isn't frontmost; ensure_app_focused via AX resolves this
- Right-click command now returns full context menu tree inline
  with ref_ids on all menuitems for immediate agent interaction
- Remove surface-based menu detection (menu lives in regular tree)
- Clean up debug tracing from resolve.rs
2026-02-21 15:40:03 -08:00
Lahfir
595ccb6cc4 feat: 10-step scroll chain, focus guards, enhanced click chain, bounds fix
- Rewrite ax_scroll with 10-step AX-first chain (scroll-to-visible,
  increment/decrement, page scroll, value shift, sub-element press,
  focus child, select rows, keyboard, arrow keys, CGEvent last resort)
- Add ensure_app_focused() guard before all CGEvent calls
- Enhance smart_activate with AXScrollToVisible pre-step,
  AXShowAlternateUI, AXCustomActions, and keyboard space fallback
- Fix bounds_hash always null in RefMap (always read bounds in builder)
- Add right-click menu capture in response data
- Add append_surface_refs for post-action menu detection
2026-02-21 15:28:28 -08:00
Lahfir
2f495ffb69 fix: address code review findings (double-free, CF leaks, injection)
P1: fix double-free in find_scroll_area (el.clone() instead of AXElement(el.0))
P1: add input validation in close_app_impl to prevent AppleScript injection
P2: fix CFRelease leaks in focus_window_impl and press_for_app_impl
P2: remove duplicated copy_element_attr/copy_bool_attr from surfaces.rs
P3: replace is_attr_settable_pub wrapper with direct pub export
P3: extract TOGGLEABLE_ROLES constant, collapse try_parent_activation loop
P3: fix fragile super::super::builder path, tighten list_windows_impl visibility
2026-02-21 14:01:51 -08:00
Lahfir
a2319623b4 fix: add menubar surface, fix press --app crash and modifier mapping
- Add --surface menubar to expose full menu bar hierarchy
- Fix use-after-free in press_for_app_impl (ManuallyDrop)
- Fix AX modifier bit mapping (Shift=1<<0, Alt=1<<1)
- Improve alert detection to search all app windows
2026-02-21 00:21:40 -08:00
Lahfir
74242f5040 fix: remove AXShowDefaultUI from activation chain, fix child walk
AXShowDefaultUI returns success on Finder sidebar rows but doesn't
navigate — it's a presentation action, not activation. Remove it so
the chain falls through to child activation which fires AXOpen on the
child AXCell, triggering actual navigation.

Also expand child activation to try AXOpen and AXShowDefaultUI on
children (not just AXPress/AXConfirm), and fire-and-forget since some
apps navigate as side effect even on error return codes.
2026-02-21 00:03:46 -08:00
Lahfir
4616c8f65f feat: smart AX-first click chain + macOS crate restructure
Restructure crates/macos/src/ from flat files into tree/, actions/,
input/, system/ subfolders. No file exceeds 400 LOC.

Add actions/activate.rs with a 10-step discovery-based activation chain:
steps 1-9 are pure AX (AXPress, AXConfirm, AXOpen, AXPick, AXSelected,
AXSelectedRows on parent, focus+retry, child walk, parent walk),
step 10 falls back to CGEvent only when every AX path fails.

Wire smart_activate() into Click/DoubleClick/RightClick/TripleClick/Toggle.
Add AXDisclosing attribute fallback for Expand/Collapse.
Rewrite README with full command reference.
2026-02-20 23:53:05 -08:00
Lahfir
9745887e12 docs: add platform crate folder structure convention to CLAUDE.md 2026-02-20 23:32:20 -08:00
Lahfir
b2c34f6ec5 Revert "docs: add platform crate folder structure convention and track plans"
This reverts commit 775892ee8e.
2026-02-20 23:31:50 -08:00
Lahfir
775892ee8e docs: add platform crate folder structure convention and track plans
Add Platform Crate Folder Structure section to CLAUDE.md enforcing
consistent subfolder layout (tree/, actions/, input/, system/) across
all platform crates. Update smart AX-first click chain plan with
new file paths reflecting the subfolder structure. Un-ignore
docs/plans/ and docs/brainstorms/ so design documents are tracked.
2026-02-20 23:26:56 -08:00
Lahfir
198d7d7d27 fix: ancestor-path cycle detection + CGEvent click fallback
Two critical fixes for macOS accessibility tree traversal:

1. Pointer reuse bug: macOS returns different logical AX elements
   sharing the same AXUIElementRef pointer value across sibling
   branches (e.g., split view panes). The global visited set
   treated these as duplicates, silently dropping entire UI
   sections (search fields, content panes, toolbars).

   Fix: changed build_subtree and find_element_recursive from
   global visited sets to ancestor-path sets. Pointers are added
   on entry and removed after children are processed, allowing
   the same pointer in different branches while still preventing
   true ancestor→descendant cycles.

   Impact: System Settings snapshot went from 45 to 121 refs,
   now exposing sidebar, search, toolbar, AND content pane.

2. CGEvent click fallback: when AXPress/AXConfirm fail (common
   for treeitems, custom SwiftUI controls), read live bounds
   and synthesize a CGEvent mouse click at the element center.

   Fallback chains: Click (AXPress→AXConfirm→CGEvent),
   DoubleClick (AXOpen→AXPress×2→CGEvent), RightClick
   (AXShowMenu→CGEvent), TripleClick, Toggle, Check/Uncheck.
2026-02-20 22:29:58 -08:00
Lahfir
48ea80c65f
Merge pull request #2 from lahfir/claude/add-performance-benchmarks-7lyog 2026-02-20 08:47:26 -08:00
Claude
ad9256d888
docs: add performance benchmarks section documenting 6s → 0.3s snapshot speedup
Documents the root cause (sequential AX IPC calls per node), the fix
(AXUIElementCopyMultipleAttributeValues batch fetching), measured wall-time
benchmarks across TextEdit/Finder/System Settings/Xcode, and the secondary
optimisations: FxHashSet cycle detection, streaming JSON serialisation, and
per-element messaging timeout.
2026-02-20 16:45:59 +00:00
Lahfir
ccbf736f9b chore: remove agentic test notes from tracking 2026-02-19 21:49:04 -08:00
Lahfir
d3f7e03c67
Merge pull request #1 from lahfir/feat/ax-first-bug-fixes-new-commands
feat: add 19 new commands, AX-first rewrites, LOC compliance
2026-02-19 21:47:56 -08:00
Lahfir
3a796023f0 style: apply cargo fmt to all files 2026-02-19 20:00:18 -08:00
Lahfir
eca04e8392 feat: add 19 new commands, AX-first rewrites, LOC compliance
- New commands: check, uncheck, triple-click, scroll-to, clear,
  clipboard-clear, hover, drag, mouse-move/click/down/up,
  resize/move/minimize/maximize/restore-window, key-down, key-up
- Rewrite input.rs to AX-first keyboard synthesis
- Add mouse synthesis (CGEvent) and window ops (AX) adapter methods
- Split dispatch.rs into dispatch + batch_dispatch for LOC compliance
- Split actions.rs → action_extras.rs (scroll/select helpers)
- Split app_ops.rs → key_dispatch.rs (press-for-app + key dispatch)
- Split cli.rs → cli_args.rs (arg structs)
- Improve is-check with applicability field, list-apps with wrapped shape
- Enhance wait with --text/--menu/--menu-closed support
2026-02-19 18:17:15 -08:00
Lahfir
2c9aee3979 fix: right-click uses AXShowMenu; context menus detected via focused element
AXShowMenu is the accessibility-native way to trigger a context menu on
an element without moving the cursor or posting CGEvents. After the
action, the resulting AXMenu appears as a child of the triggered element.

- actions.rs: RightClick tries AXShowMenu first, falls back to CGEvent
- surfaces.rs: context_menu_from_app checks AXFocusedUIElement's children
  for AXMenu, then falls back to direct app children (Electron apps)
- list_surfaces_for_pid: surfaces context menus as 'context_menu' type
  (distinct from menu-bar 'menu' entries)
2026-02-19 15:45:52 -08:00
Lahfir
7f0d6103d1 fix: detect open menus via AXMenuBarItem.AXSelected, not AXMenus attribute
AXMenus on AXApplication returns kAXErrorAttributeUnsupported (-25205)
for standard Cocoa apps. Menu bar menus are always children of their
AXMenuBarItem; the only reliable open-state indicator is AXSelected=true
on that item.

- open_menubar_menu: scan AXMenuBar.AXChildren for AXMenuBarItem with
  AXSelected=true, return its AXMenu child
- context_menu_from_app: kept as fallback for Electron-style apps that
  do expose context menus as AXMenu in AXApplication.AXChildren
- list_surfaces_for_pid: reports menu title and item_count when open
- is_menu_open: checks both paths
2026-02-19 15:40:05 -08:00
Lahfir
39178b2916 feat: surface-targeted snapshot, menu wait, list-surfaces command
Add snapshot --surface flag (menu/sheet/popover/alert/focused/window)
for direct O(1) AX attribute reads rather than full-tree traversal.
Add wait --menu/--menu-closed for polling-based context-menu gate.
Add list-surfaces command to enumerate open transient surfaces.
Remove all inline // comments from macos crate per 400-LOC/no-comment rule.
2026-02-19 15:21:07 -08:00
Lahfir
f1ed36046c refactor: Phase A quality fixes — dead code, bugs, LOC compliance
- Delete clipboard.rs (superseded by clipboard_get/set; zero callers)
- Remove batch::execute() stub (dispatch layer owns batch execution)
- Fix wait.rs: replace double get+unwrap with if-let pattern
- Fix press.rs: replace dead is_empty check + unwrap with ok_or_else
- Add doc comment to is_check.rs documenting stale-state semantics
- Trim tree.rs to 394 LOC (was 403; compress non-macos stub + remove redundant comment)
- Move probe binaries to examples/ with required-features = ["dev-tools"]
- Fix clippy::explicit_auto_deref in adapter.rs
- Fix clippy::needless_borrows_for_generic_args in screenshot.rs
- Apply prior session core/macos fixes (adapter, actions, roles, snapshot, get)

All targets pass cargo clippy -D warnings.
2026-02-19 15:04:18 -08:00
Lahfir
1d98ab828c fix: make all 30 commands work end-to-end on macOS
- tree: window_element_for() starts traversal from correct AXWindow
  element (matching by title) instead of app root, fixing mixed-window
  trees and 'disabled group' noise in Electron apps
- tree: AXUIElementCopyMultipleAttributeValues batch fetch (2.5x faster)
- tree: copy_ax_array() CFRetains each element before CFArray drops,
  fixing the dangling-pointer bug that returned kAXErrorInvalidUIElement
- roles: add AXApplication->application and AXSplitGroup->splitter
- actions: CGEvent is now primary click mechanism (AXPress is best-effort
  first try); fixes clicks in Electron/web apps that don't support kAXPressAction
- actions: DoubleClick uses MOUSE_EVENT_CLICK_STATE=2, RightClick uses
  RightMouseDown/Up, Scroll sets event.set_location(element_center)
- screenshot: rewrite using screencapture CLI + CGWindowListCopyWindowInfo
  to find the largest CGWindowID for the app PID; resolves app/window_id
  to pid before calling adapter (was producing all-zero data placeholder)
- screenshot: ScreenshotTarget::Window now carries pid (i32) instead of
  our hash string; commands/screenshot.rs resolves via list_windows
- input: add 8ms inter-keystroke delay in synthesize_text so apps can
  process events (was truncating long strings)
- snapshot: return WindowInfo in SnapshotResult; include app and window
  fields in JSON output per contract
- find: switch to snapshot::run so found refs are persisted to refmap
- dispatch: implement dispatch_batch_command covering all 29 commands
  (was stub returning 'not yet implemented' for every batch sub-command)
2026-02-19 12:51:37 -08:00
Lahfir
6dc567a4ae fix: align error codes with spec (APP_NOT_FOUND, PERM_DENIED) and add -i shorthand 2026-02-19 11:49:17 -08:00
Lahfir
fb34600448 docs: add professional README with full command reference and architecture guide 2026-02-19 11:44:28 -08:00
Lahfir
fe501a1a20 docs: mark all 47 code review todos as complete 2026-02-19 11:39:21 -08:00
Lahfir
218503a7eb fix: resolve all 47 code review findings from Phase 1 audit
Security (P1):
- Replace AppleScript app-name interpolation with PID-based scripts (001, 002)
- Switch pkill -f regex to pkill -x exact-name match (003)
- Validate launch_app id against path traversal (006)
- Remove CFRetain+mem::forget; use ManuallyDrop correctly (005, 029)
- Add cycle detection (visited set) to resolve_element (009)

Correctness (P1/P2):
- Implement read_bounds via kAXPositionAttribute+kAXSizeAttribute (023)
- Fix Scroll action to use CGEventCreateScrollWheelEvent (027)
- Propagate Expand/Collapse AX errors instead of discarding (042)
- Store AXDescription separately from AXTitle (046)
- Fix interactive_only to recurse into container children (043)
- Pass window.pid to RefEntry instead of hardcoded 0 (019)
- Add batch::parse_commands() for dispatch-layer batch execution (022)
- Fix find command to use snapshot::build() — no RefMap overwrite (030)
- Return non-interactive elements from find with ref:null (044)
- Wire permissions --request to adapter.check_permissions (031)
- Fix screenshot --app to resolve window ID (032)
- Close-app protected process check uses exact match (014)
- Wait unbounded sleep capped at 30s (012)
- Fix wait double-lookup unwrap (038)
- Fix press.rs unwrap, normalize modifier order (021, 038)
- emit_json no longer silently discards write errors (041)

Data / API:
- WindowInfo.app serializes as "app_name" per spec (045)
- Add value/states/bounds fields to RefEntry; populate from snapshot (024, 025)
- Stable window IDs via FxHasher(pid+title) (028)
- Use FxHasher for bounds_hash, replacing unstable DefaultHasher (020)
- Flush refmap temp file before rename (039)
- Add HOME fallback to USERPROFILE (015)

Architecture:
- Split click.rs → click, double_click, right_click per one-command-per-file (047)
- Split clipboard.rs → clipboard_get, clipboard_set (047)
- Consolidate RefArgs to helpers.rs (047)
- Move focus/launch/close impl to app_ops.rs; adapter.rs 389→311 LOC (001)
- Deduplicate ErrorCode::code() via ErrorCode::as_str() (034)
- Add doc comment to Response struct explaining Phase 3 intent (035)
- Add snapshot::build() for read-only tree access (030)
- Remove duplicate ABSOLUTE_MAX_DEPTH from snapshot.rs (033)
2026-02-19 11:38:17 -08:00
Lahfir
608d4aaaa1 fix: suppress dead_code lint on BatchCommand deserializer struct 2026-02-19 10:46:47 -08:00