Skip to content

feat: ui-helper (windows/displays/permissions/captureScreen) + extended Vision API - #9

Merged
woladi merged 7 commits into
mainfrom
feat/ui-helper
Aug 22, 2026
Merged

woladi merged 7 commits into
mainfrom
feat/ui-helper

Conversation

@woladi

@woladi woladi commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Adds ui-helper as the package's third prebuilt native helper and exports a screen-capture API, plus a broad extension of the Vision surface.

ui-helper — the piece macos-vision-mcp is waiting on

  • listWindows, listDisplays, checkPermissions, captureScreen (window, region, display; returns path + geometry + sha256, never image bytes).
  • Ships through the existing prebuilt-binary pipeline (install-native.js / build-native-cross.js), so consumers do not need Xcode at runtime.
  • checkPermissions reports screenLocked; capture failures consult it, so a locked Mac is diagnosed outright instead of guessed at.

Extended Vision API

recognizeDocument (macOS 26 — native paragraphs/tables/lists/detected data), extractEntities, compareImages, detectTextRegions, imageInfo, visionCapabilities, pose/face/animal detection, saliency, contours, aesthetics, lens smudge, and pixel ops that return paths (cropImage, cropDocument, extractForeground, personMask). OCR gains language, ROI, custom-word and correction options plus an opt-in content-hash cache.

Compatibility

No breaking changes. macOS 12 and Node 18 support are retained: modern APIs are gated twice, at compile time on the SDK (-DSDK_14/15/26) and at runtime on #available. Release builds now refuse an SDK too old to compile in every gated feature, so a CI image change cannot silently strip them.

Verification

82/82 tests pass. vitest.config.ts added — the suite was flaky because model-bound tests ran under the default 5s timeout.

🤖 Generated with Claude Code

woladi and others added 7 commits August 22, 2026 06:33
- src/native/ui-helper.swift: read-only CoreGraphics introspection helper
  (CGGetOnlineDisplayList so asleep displays stay visible; no AppKit)
- scripts: ui-helper joins vision-helper/pdf-helper in the prebuilt pipeline
- src/ui.ts: listWindows / listDisplays / checkPermissions / captureScreen
  (screencapture-based, returns paths + geometry, never image bytes)
- README: UI API section with privacy invariant (eyes, not hands)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nd pixel ops

vision-helper (macOS 13 target, newer features behind #available):
- OCR options: --lang, --auto-lang, --no-correction, --custom-words, --fast,
  --roi (results mapped back to full-image space), --min-text-height
- --document-structure (macOS 26, RecognizeDocumentsRequest): title, paragraphs,
  tables with spans, lists, barcodes, detected data with positions
- --entities (NSDataDetector over stdin), --text-rects, --compare (feature
  print distance), --image-info, --languages, --capabilities
- people/scenes: --face-landmarks (+capture quality, head pose), --humans,
  --body-pose, --hand-pose, --animals, --animal-pose, --horizon, --contours,
  --saliency attention|objectness (+heatmap), --aesthetics (15+), --smudge (26+,
  reports supported:false when the model is absent on this hardware)
- pixel ops returning paths: --crop, --document-crop (CIPerspectiveCorrection),
  --foreground-mask (14+), --person-mask

TypeScript:
- src/vision.ts: typed wrappers for everything above, UnsupportedOnThisMacOSError
- ocr(): TextRecognitionOptions, opt-in content-hash cache, onProgress for PDFs
- captureScreen(): sha256 of the PNG
- CLI: OCR tuning flags, --structure/--entities/--text-regions/--humans/
  --face-landmarks/--saliency/--aesthetics/--info/--capabilities/--languages
- tests: test/vision.test.ts (22 cases); README; changeset (minor); node >= 20

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ft/TS boilerplate

- src/helper.ts: one execHelper/runHelper/runGated for all three binaries
  (timeouts, buffer, exit-code-2 → UnsupportedOnThisMacOSError), tmpOutPath,
  sha256/fileSha256 — replaces three divergent runners
- vision-helper: fd 1 diverted to stderr process-wide, JSON written to the saved
  stdout (one payload, no per-mode dup2 hack); perform()/fail() replace 19
  copy-pasted catch blocks; Box {x,y,width,height,confidence} unifies
  Face/TextRect/Human/DocRegion so TS needs no RawBox/DocBox mapping layer;
  supportedLanguages(), macOS15/26 flags, hoisted ISO8601 formatter
- ocr cache key = content hash + canonical textOptionArgs (order-independent);
  onProgress moved to OcrOptions; ocrPdf typed as Omit<OcrOptions,'format'>
- captureScreen reads the PNG once (IHDR + sha256 from one buffer)
- CLI: top-level await early exit for --capabilities/--languages (one spawn),
  single memoized OCR pass shared by --ocr/--entities, CLASSIC_FLAGS table
- visionCapabilities() memoized per process; ui-helper hoists --all check

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… helper errors

Found by an end-to-end pass over the built package:

- detectContours still mapped the pre-refactor c.w/c.h wire names, so every
  contour came back with width/height undefined. Returns the helper shape now,
  with a regression test asserting width/height on every rect-shaped result.
- vision-helper referenced symbols introduced in macOS 14/15/26 SDKs
  unconditionally, so the swiftc fallback in install-native.js could not build
  on an older Mac (#available guards runtime, not the SDK). Modern paths are now
  behind -DSDK_14/15/26, which both build scripts derive from
  "xcrun --sdk macosx --show-sdk-version"; a build without them still compiles,
  reports the feature as false in --capabilities and exits 2, which maps to
  UnsupportedOnThisMacOSError. Verified by compiling all four SDK tiers.
- Helper failures leaked Node's "Command failed: /path/to/vision-helper --flags"
  plus a stack trace. execHelper now surfaces the helper's own ERROR line while
  preserving the exit status (so gating still works) and stderr (so
  captureScreen can still quote screencapture).

Tests: 82 unit (6 new: wire-shape, capability gating, error reporting).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review of feat/ui-helper found three blockers before release:

- The branch dropped macOS 12 (native target 12→13) and Node 18 under a
  `minor` changeset, so a `^1.5.0` consumer on macOS 12 would auto-upgrade
  into binaries that cannot launch. The platform drop traced to a single
  un-gated `automaticallyDetectsLanguage` call (macOS 13+); wrapping it in
  `if #available` — the pattern the file already uses for 14/15/26 — lets all
  three helpers build at macos12 with every gated feature still enabled.
  The Node bump was unrelated to this branch (`fetch`/`AbortSignal.timeout`
  in ollama.ts predate it and ran fine on 18).

- The suite was red: two pre-existing detectDocument tests timed out at the
  default 5s when the new model-bound tests ran alongside them, though they
  pass in isolation and the helper itself takes 0.12s. Added vitest.config.ts
  (30s timeout, no file parallelism, worktree copies excluded): 82/82 pass.

- Release builds now refuse an SDK too old to compile in every gated feature,
  instead of silently shipping binaries that report them unavailable.

Also: imageAesthetics uses runGated() like its siblings instead of
hand-checking the exit code; the OCR cache key includes helper and macOS
version so entries do not survive an upgrade that changes results; corrected
the comment above unROI, which claimed the opposite of what the code does
(verified empirically that Vision reports ROI-relative coordinates).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Keeps this package in step with the consumer that will eventually drop
its own copy of ui-helper. On a locked Mac, window and region capture
fail outright and a full-screen capture returns only the lock screen —
verified against a genuinely locked session. checkPermissions now
reports screenLocked so a caller can check before starting, and
captureScreen consults it on failure to say plainly that retrying cannot
succeed until someone unlocks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
detectLensSmudge documents that it returns `supported: false` rather than
guessing, and the helper already detected hardware without the model by
sniffing VisionCore's stdout notice. CI runners fail differently: the
request throws Foundation._GenericObjCError.nilError, which took the
fail() path and turned a "this Mac cannot do it" into a hard error —
turning the suite red on GitHub while passing locally.

The image has already loaded by that point, so a throw here can only mean
the model is unusable on this machine. Report it the same way as the
notice path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@woladi
woladi merged commit 2b576bf into main Aug 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant