Skip to content

Add apple-tv-remote extension - #28594

Merged
raycastbot merged 9 commits into
raycast:mainfrom
camschroedes:ext/apple-tv-remote
Jul 1, 2026
Merged

Add apple-tv-remote extension#28594
raycastbot merged 9 commits into
raycast:mainfrom
camschroedes:ext/apple-tv-remote

Conversation

@camschroedes

@camschroedes camschroedes commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Description

Apple TV Remote — control an Apple TV from Raycast with zero external installs (no Python/pyatv, no helper apps). Pure TypeScript over Apple's Companion protocol, via @bharper/atv-js (a pyatv port — all pure-JS dependencies, no native addons).

This is the first Apple TV remote in the store. What it does:

  • Setup: Bonjour discovery → on-screen 4-digit PIN pairing (HAP SRP), with manual IP/port fallback for restricted networks. Credentials live in Raycast's encrypted LocalStorage (no Keychain).
  • Apple TV Remote (view): a clickable remote grid holding a persistent connection — instant keypresses. Bare-key layer (WASD/HJKL move, F select, Space play/pause…) plus ⌥-shortcuts. Includes context menu (hold-select), app switcher, Control Center, ±10s skip, and typing into TV search fields.
  • Launch Apple TV App (view): grid of the device's actual installed apps, with per-app Create Quicklink for global hotkeys.
  • Ask Apple TV (no-view + argument): one-shot natural language — pause, open netflix, play severance, type stranger things, sleep.
  • Menu bar: quick taps + one-click access to the full remote. Actions delegate to background command launches so the menu's unload-on-close lifecycle can't kill in-flight requests.
  • Per-key commands (mostly disabledByDefault, Spotify/Sonos pattern): every remote function individually hotkey-able.
  • AI extension: 4 tools (remote-key, launch-app, play-content, power) + evals. play-content resolves titles to real deep links via JustWatch's public GraphQL (no LLM-guessed IDs); Netflix (whose tvOS deep links no longer work) and unresolved titles fall back to typing into tvOS universal Search, gated by a Search Automation preference.

All device interactions were verified against a physical Apple TV 4K (tvOS 18). Known protocol caveats are documented in the README (volume requires HDMI-CEC; screensaver flaky upstream — pyatv #2139).

The protocol features missing from the underlying library (app launch/list, power, Control Center, skip) are implemented in src/lib/companion-extras.ts, ported from pyatv's reference implementation — and contributed upstream in bsharper/atvjs#1 (incl. an OPACK negative-integer crash fix found during this work).

Screencast

Screenshots in metadata/ (remote grid, command roster, menu bar). Happy to add a screencast if helpful — note the extension requires a physical Apple TV on the local network, so reviewer testing needs one; the README's setup section covers the 30-second pairing flow.

Checklist

- README: embed store screenshots
- Replace store screenshots with improved captures
- Add store screenshots (remote, command roster, menu bar)
- Pre-ship Raptor pass: remove Spotify, dead code, and fix all docs
- Fix skip-back (OPACK negative-int crash) + remove volume from visible surfaces
- Menu bar: fire "skip forward" not "skip" (which maps to next track)
- Fix menu bar clicks never reaching the TV
- Remote view: compact 5-column cross layout
- Menu bar: custom TV icon with light/dark variants
- Menu bar: use emoji title — Icon.Tv rendered invisible in the status bar
- Menu bar: remote functions as menu rows (the platform maximum)
- Visual remote: grid laid out like the physical Apple TV remote
- Full Siri Remote parity + instant-access architecture
- Spotify Connect: verified agentic music playback on the Apple TV
- Search automation ladder: stop on the title page by default
- Fix region handling: auto-detect country from macOS locale; honor explicit provider
- Deterministic content resolution: JustWatch deep links + tvOS universal search flow
- Add Ask Apple TV one-shot command; improve AI tool routing; manual-port pairing
- Initial release: Apple TV Remote extension for Raycast
@raycastbot raycastbot added new extension Label for PRs with new extensions AI Extension labels Jun 6, 2026
@raycastbot

Copy link
Copy Markdown
Collaborator

Congratulations on your new Raycast extension! 🚀

We're currently experiencing a high volume of incoming requests. As a result, the initial review may take up to 15 business days.

Once the PR is approved and merged, the extension will be available on our Store.

@camschroedes
camschroedes marked this pull request as ready for review June 6, 2026 10:25
@greptile-apps

greptile-apps Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a fully self-contained Apple TV remote extension — no Python or external helpers required — using @bharper/atv-js over Apple's Companion protocol. It covers a visual grid remote with a persistent connection, app launcher, natural-language ask command, menu bar quick controls, per-key Raycast commands, and AI tools backed by JustWatch deep-link resolution.

  • The core architecture (connection coalescing, mount-guard, identity-checked onConnectionLost, orphaned-socket cleanup) is solid and the previously flagged concurrency issues have been properly addressed.
  • The skip-forward/skip-back float-nudge workaround in companion-extras.ts cleanly avoids touching library internals now that the direct opack import was removed.
  • One lint-blocking duplicate import of @bharper/atv-js in ask.ts needs to be merged before ray lint will pass.

Confidence Score: 4/5

The extension is well-constructed but has one lint-blocking duplicate import in ask.ts that must be fixed before the PR can merge cleanly.

The duplicate @bharper/atv-js import in ask.ts (lines 2 and 15) is caught by ESLint's no-duplicate-imports rule, which @raycast/eslint-config enables — ray lint will reject it. Everything else in the core connection, companion-extras, play-flow, and UI paths looks correct.

extensions/apple-tv-remote/src/ask.ts (duplicate import), extensions/apple-tv-remote/CHANGELOG.md (hardcoded date), extensions/apple-tv-remote/package.json (Volume up capitalisation)

Important Files Changed

Filename Overview
extensions/apple-tv-remote/src/ask.ts Natural-language dispatch command; has a duplicate @bharper/atv-js import (lines 2 & 15) that will fail ray lint
extensions/apple-tv-remote/src/remote.tsx Persistent-connection view remote; connection coalescing, mount-guard, and stale-callback identity checks are all correctly implemented
extensions/apple-tv-remote/src/lib/connection.ts openConnection/withConnection helpers; timeout, orphan-socket disposal, and DHCP-update save logic look correct
extensions/apple-tv-remote/src/lib/companion-extras.ts Protocol extensions for app launch, HID commands, long-press, skip; float-nudge workaround is clean and well-documented
extensions/apple-tv-remote/src/lib/play-flow.ts JustWatch→deep-link→search-type fallback chain; preference reads correctly use auto-generated Preferences type
extensions/apple-tv-remote/src/lib/justwatch.ts GraphQL title resolution with 24h LocalStorage cache; country auto-detection via /usr/bin/defaults is fine on macOS
extensions/apple-tv-remote/src/menubar.tsx Menu-bar command; correctly delegates all device actions to background ask launches to survive menu unload
extensions/apple-tv-remote/src/setup.tsx Bonjour scan + Companion HAP-SRP pairing flow; cancellation guards on both scan and pairing effects are correct
extensions/apple-tv-remote/src/launch-app.tsx App-grid view with live fetch falling back to cache then static KNOWN_APPS; Quicklink creation uses createDeeplink correctly
extensions/apple-tv-remote/package.json Well-structured extension manifest; 'Volume up' title is missing capitalisation on the second word
extensions/apple-tv-remote/CHANGELOG.md Changelog uses a hardcoded date and non-standard section title instead of the required {PR_MERGE_DATE} placeholder
extensions/apple-tv-remote/src/lib/deep-links.ts KNOWN_APPS registry and resolveAppName fuzzy matcher; logic is sound, LocalStorage app-cache helpers are correct
extensions/apple-tv-remote/src/lib/errors.ts Custom error classes and showErrorToast helper; pairing-expiry heuristic and launchCommand fallback to Setup look correct

Reviews (7): Last reviewed commit: "Update CHANGELOG.md and optimise images" | Re-trigger Greptile

Comment thread extensions/apple-tv-remote/src/lib/companion-extras.ts
Comment thread extensions/apple-tv-remote/src/lib/connection.ts Outdated
Comment thread extensions/apple-tv-remote/src/lib/justwatch.ts Outdated
Comment thread extensions/apple-tv-remote/src/lib/play-flow.ts Outdated
- docs/style: rewrite README + description in plain voice, remove all em-dashes
- revert: drop menu-bar Now Playing (requires a second pairing)
- feat: live Now Playing in the menu bar (MRP-over-AirPlay, zero-install)
Comment thread extensions/apple-tv-remote/src/remote.tsx
@0xdhrv 0xdhrv self-assigned this Jun 29, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: check the metadata screenshot size and padding

Can you make sure the metadata screenshot is the correct size and has the proper padding?

Right now it looks cropped and has a white border. Please use the capture window command in Raycast.

Reference:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recaptured with Window Capture at a clean 2000×1250 — no white border, nothing cropped. The original was a manual screenshot that got letterboxed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: check the metadata screenshot size and padding

Can you make sure the metadata screenshot is the correct size and has the proper padding?

Right now it looks cropped and has a white border. Please use the capture window command in Raycast.

Reference:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same fix — recaptured via Window Capture at 2000×1250.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: not needed

You can move the menu bar command to the media folder instead of removing it entirely, as it may be useful in that specific location.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved out of the store screenshots into media/ (referenced from the README there) and removed from metadata/, since you noted it wasn't needed.

@0xdhrv 0xdhrv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @camschroedes 👋

I have added a few comments for you to address.

I'm looking forward to testing this extension again 🔥

Feel free to contact me here or at Slack if you have any questions.


I converted this PR into a draft until it's ready for the review, please press the button Ready for review when it's ready and we'll have a look 😊

Resolves the open comments on raycast#28594:
- Fix concurrent establish() connection leak in remote.tsx: coalesce
  concurrent callers onto one in-flight attempt, identity-check the
  onConnectionLost callback, dispose a socket that resolves after
  unmount, and retire the previous connection on manual reconnect.
- connection.ts: dispose a connect() that resolves after the timeout.
- Recapture store screenshots at a clean 2000x1250 via Window Capture
  (no white border / cropping); drop the menu-bar store screenshot
  (kept in media/ for the README).

Co-Authored-By: Cameron Schroeder <64573040+camschroedes@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@camschroedes

Copy link
Copy Markdown
Contributor Author

Thanks for the review @0xdhrv! Addressed everything:

  • Connection leak (remote.tsx): concurrent establish() calls now coalesce onto one in-flight attempt, the onConnectionLost callback identity-checks the active connection, and there's a mount guard for sockets that resolve after unmount. connection.ts also disposes a connect() that resolves past the timeout.
  • Screenshots: recaptured the remote + commands shots at a clean 2000×1250 via Window Capture (no border/cropping); dropped the menu-bar store screenshot and kept it in media/ for the README.

On the Greptile summary's usedCredentialsMatchProvided note — that one's a false positive. It's a declared boolean on AppleTVConnection, set on every return path in @bharper/atv-js@0.3.4 (dist/index.d.ts + dist/index.js), so the access is safe.

Marking ready for review 🙏

@camschroedes
camschroedes marked this pull request as ready for review June 30, 2026 21:37
camschroedes added a commit to camschroedes/raycast-apple-tv-remote that referenced this pull request Jun 30, 2026
Addresses the metadata-screenshot comments on raycast/extensions#28594:
- Recapture remote (d-pad) + commands screenshots via Raycast Window
  Capture: clean 2000x1250, no white border, no cropping
  -> apple-tv-remote-1.png, apple-tv-remote-2.png
- Drop the menu-bar store screenshot (apple-tv-remote-3.png); reviewer
  said it was not needed and it already lives in media/ for the README
- Sync media/remote.png + media/commands.png to the new captures

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

T-Rex pricing update — T-Rex was free through June 2026. Effective July 1, 2026, T-Rex adds 2 credits on top of the standard 1-credit review (3 total). T-Rex settings

- Add try-catch to openSetup for improved error feedback
- Update .gitignore to include compiled_raycast_rust
- Revise CHANGELOG for consistent version format
- Adjust README and package.json for clarity and accuracy

@0xdhrv 0xdhrv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, approved ✅

@raycastbot
raycastbot merged commit b1e5cae into raycast:main Jul 1, 2026
3 checks passed
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Published to the Raycast Store:
https://raycast.com/camschroedes/apple-tv-remote

@raycastbot

Copy link
Copy Markdown
Collaborator

🎉 🎉 🎉

We've rewarded your Raycast account with some credits. You will soon be able to exchange them for some swag.

@camschroedes
camschroedes deleted the ext/apple-tv-remote branch July 3, 2026 03:48
almatkai pushed a commit to almatkai/raymes-extensions that referenced this pull request Jul 13, 2026
* Add apple-tv-remote extension

- README: embed store screenshots
- Replace store screenshots with improved captures
- Add store screenshots (remote, command roster, menu bar)
- Pre-ship Raptor pass: remove Spotify, dead code, and fix all docs
- Fix skip-back (OPACK negative-int crash) + remove volume from visible surfaces
- Menu bar: fire "skip forward" not "skip" (which maps to next track)
- Fix menu bar clicks never reaching the TV
- Remote view: compact 5-column cross layout
- Menu bar: custom TV icon with light/dark variants
- Menu bar: use emoji title — Icon.Tv rendered invisible in the status bar
- Menu bar: remote functions as menu rows (the platform maximum)
- Visual remote: grid laid out like the physical Apple TV remote
- Full Siri Remote parity + instant-access architecture
- Spotify Connect: verified agentic music playback on the Apple TV
- Search automation ladder: stop on the title page by default
- Fix region handling: auto-detect country from macOS locale; honor explicit provider
- Deterministic content resolution: JustWatch deep links + tvOS universal search flow
- Add Ask Apple TV one-shot command; improve AI tool routing; manual-port pairing
- Initial release: Apple TV Remote extension for Raycast

* README images: move to media/ (metadata/ is store-screenshots-only)

* Fix store CI: version-safe icons + metadata padding spec

* Fix store CI (validated locally) + Greptile review feedback

* Update apple-tv-remote extension

- docs/style: rewrite README + description in plain voice, remove all em-dashes
- revert: drop menu-bar Now Playing (requires a second pairing)
- feat: live Now Playing in the menu bar (MRP-over-AirPlay, zero-install)

* fix: Raycast store-guideline compliance pass

* apple-tv-remote: address review (connection-leak fix + screenshots)

Resolves the open comments on raycast#28594:
- Fix concurrent establish() connection leak in remote.tsx: coalesce
  concurrent callers onto one in-flight attempt, identity-check the
  onConnectionLost callback, dispose a socket that resolves after
  unmount, and retire the previous connection on manual reconnect.
- connection.ts: dispose a connect() that resolves after the timeout.
- Recapture store screenshots at a clean 2000x1250 via Window Capture
  (no white border / cropping); drop the menu-bar store screenshot
  (kept in media/ for the README).

Co-Authored-By: Cameron Schroeder <64573040+camschroedes@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(apple-tv-remote): enhance error handling and docs update
- Add try-catch to openSetup for improved error feedback
- Update .gitignore to include compiled_raycast_rust
- Revise CHANGELOG for consistent version format
- Adjust README and package.json for clarity and accuracy

* Update CHANGELOG.md and optimise images

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Dhruv Suthar <git@dhrv.pw>
Co-authored-by: raycastbot <bot@raycast.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Extension new extension Label for PRs with new extensions platform: macOS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants