Skip to content

Add quick-contact-actions extension#26717

Open
ShixAJ wants to merge 3 commits intoraycast:mainfrom
ShixAJ:ext/quick-contact-actions
Open

Add quick-contact-actions extension#26717
ShixAJ wants to merge 3 commits intoraycast:mainfrom
ShixAJ:ext/quick-contact-actions

Conversation

@ShixAJ
Copy link
Copy Markdown

@ShixAJ ShixAJ commented Mar 28, 2026

Description

Search your macOS contacts and launch calls, messages, and emails instantly from
Raycast.

Features

  • Spotlight-style search with smart scoring — matches by name, phone digits,
    or email
  • Quick actions — FaceTime Video/Audio, Phone Call, iMessage, Email
  • Detail panel (⌘D) with profile picture, name, and organization
  • Favorites (⌘⇧S) — pin contacts to the top
  • Frequently contacted — your top 5 most-used contacts appear automatically
  • Open/Edit in Contacts — jump straight to the contact in Contacts.app
  • Contact argument — type qca john to auto-navigate if there's a unique
    match

Keyboard Shortcuts

Shortcut Action
⌘⇧F FaceTime Video
⌘⇧A FaceTime Audio
⌘⇧C Call
⌘M Send Message
⌘E Send Email
⌘O Open in Contacts
⌘⇧O Edit in Contacts
⌘D Toggle Detail Panel
⌘⇧S Toggle Favorite
⌘. Copy Name
⌘⇧. Copy Phone Number
⌘⇧E Copy Email

Permissions

  • Contacts — required to read your contacts. macOS will prompt automatically
    on first use.
  • Accessibility — required for "Edit in Contacts" (sends ⌘L keystroke to
    Contacts.app via System Events). macOS will prompt when first used.

Screencast

image qcacap image image

Checklist

- Fix icon and reorganize action panel sections
- Prepare for publishing: README, changelog, clean up tracked files
- Prepare for Raycast Store publishing
- Fix Open/Edit in Contacts to use AppleScript
- Add favorite contacts feature (⌘⇧S)
- Add Edit in Contacts action (⌘⇧O)
- Add detail pane profile picture and organization name
- Fix flicker: batch contacts and frequency into single render
- Fix selection: load frequency before showing cached contacts
- Revert detail pane to Metadata.Label with colored icons
- Revert list accessories to icons, add colored tags in detail pane
- Show primary label tags as accessories instead of count icons
- Group non-alpha contacts under # like standard phonebooks
- Group contacts by first letter in phonebook-style sections
- Fix frequently contacted: use Action with push() instead of Action.Push
- Add frequently contacted sorting with persistent tracking
- Add colored icons to ContactActions sections
- Revert Swift to original thumbnails, simplify detail panel to metadata-only
- Generate initials avatars for contacts without photos
- Use standard markdown image for circular contact photo in detail panel
- Circular contact photo in detail panel with name beside it
- Show circular contact photo in detail panel metadata
- Re-add contact photo to detail panel as small 80px image
- Remove oversized photo from detail panel, keep metadata only
- Fix detail panel image: encode spaces in file:// path
- Add detail side panel with contact info (⌘D toggle)
- Add empty search state and contact row accessories
- Add email addresses to FaceTime Video section
- Add Open in Contacts action (⌘O)
- Group copy actions into separate ActionPanel section
- Align shortcuts with Raycast built-in Contacts and add copy actions
- Fix reserved shortcut: ⌘P → ⌘⇧P for Call action
- Pre-compile Swift contact fetcher for faster startup
- Cache contacts for instant loading on repeat opens
- Add keyboard shortcuts for quick actions on main contact list
- Working state: contacts, photos, search ranking, contact argument feature
@raycastbot raycastbot added new extension Label for PRs with new extensions platform: macOS labels Mar 28, 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 10-15 business days.

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

@ShixAJ ShixAJ marked this pull request as ready for review March 28, 2026 14:43
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 28, 2026

Greptile Summary

This PR adds a new Quick Contact Actions extension that lets users search their macOS contacts and launch FaceTime, calls, messages, and emails directly from Raycast. It uses a bundled Swift binary (compiled via a prebuild npm script) to query CNContactStore natively.

Key issues to address before merging:

  • AppleScript injection (get-contacts.swift): Contact names are interpolated into AppleScript strings with only \" escaped — unescaped newlines allow code injection.
  • Silent error handling (quick-contact-actions.tsx): The "Open in Contacts" and "Edit in Contacts" execFile callbacks are empty, giving users no feedback on failure.
  • Unnecessary detail computation (quick-contact-actions.tsx): The detail prop is always evaluated for every list item on every render, even when the detail panel is hidden.

Confidence Score: 3/5

Not safe to merge — the AppleScript injection in the Swift binary is a definite code injection path that should be fixed before shipping.

The AppleScript injection via unescaped newlines is a verified P1 security defect. While exploitation requires a crafted contact in the user's own address book, the vulnerability is real and reproducible. The two P2 findings are not blockers on their own.

assets/get-contacts.swift (AppleScript injection), src/quick-contact-actions.tsx (silent errors and detail computation)

Important Files Changed

Filename Overview
extensions/quick-contact-actions/assets/get-contacts.swift Bundled Swift binary for CNContactStore access and AppleScript open/edit — has an AppleScript injection vulnerability via unescaped newlines in contact names.
extensions/quick-contact-actions/src/quick-contact-actions.tsx Main extension UI — has silent error handling for open/edit actions and unnecessary detail panel computation on every render.
extensions/quick-contact-actions/package.json Extension manifest with correct category, platform, permissions, and prebuild step to compile the Swift binary.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/quick-contact-actions/assets/get-contacts.swift
Line: 71-72

Comment:
**AppleScript injection via unescaped newlines in contact name**

The Swift code escapes double quotes in the contact name before embedding it in the AppleScript string, but does not escape newline characters (`\n`). A contact whose `givenName` or `familyName` contains a literal newline (which `CNContactStore` does not prohibit) would break out of the AppleScript string and allow arbitrary AppleScript injection.

At minimum, newlines should also be stripped or escaped:
```swift
let escaped = name
    .replacingOccurrences(of: "\\", with: "\\\\")
    .replacingOccurrences(of: "\"", with: "\\\"")
    .replacingOccurrences(of: "\n", with: " ")
    .replacingOccurrences(of: "\r", with: " ")
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/quick-contact-actions/src/quick-contact-actions.tsx
Line: 479-484

Comment:
**Silent errors on Open/Edit in Contacts actions**

The `execFile` callback is `() => {}` for both "Open in Contacts" and "Edit in Contacts", silently discarding any errors. Users receive zero feedback if the action fails. Consider showing a `Toast` on error. The same applies to the "Edit in Contacts" action on line 492.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/quick-contact-actions/src/quick-contact-actions.tsx
Line: 386-417

Comment:
**Detail panel content computed on every render for all contacts**

The `detail` prop — including the IIFE that generates SVG data URIs — is evaluated unconditionally on every render for every list item, regardless of whether `showDetail` is `true`. Guard the prop behind the `showDetail` flag so the computation is skipped when the panel is hidden.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (2): Last reviewed commit: "Update screenshots and apply reviewer ch..." | Re-trigger Greptile

<Action.Open
title="Start FaceTime Video"
icon={Icon.Video}
target={`facetime://${p.value.replace(/\s/g, "")}`}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Inconsistent phone number normalization in FaceTime URLs

The facetime:// URL only strips whitespace (.replace(/\s/g, "")), which leaves parentheses, dashes, and other formatting characters in the URL. A number like (555) 123-4567 becomes facetime://(555)123-4567, which is an invalid URL and will silently fail to launch FaceTime.

The tel: action on line 136 correctly uses .replace(/[^+\d]/g, "") to strip all non-digit/non-plus characters. The same regex should be applied here and in the facetime-audio:// URLs.

Affected lines: 65, 102, 117, 436, 444.

Suggested change
target={`facetime://${p.value.replace(/\s/g, "")}`}
target={`facetime://${p.value.replace(/[^+\d]/g, "")}`}
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/quick-contact-actions/src/quick-contact-actions.tsx
Line: 65

Comment:
**Inconsistent phone number normalization in FaceTime URLs**

The `facetime://` URL only strips whitespace (`.replace(/\s/g, "")`), which leaves parentheses, dashes, and other formatting characters in the URL. A number like `(555) 123-4567` becomes `facetime://(555)123-4567`, which is an invalid URL and will silently fail to launch FaceTime.

The `tel:` action on line 136 correctly uses `.replace(/[^+\d]/g, "")` to strip all non-digit/non-plus characters. The same regex should be applied here and in the `facetime-audio://` URLs.

Affected lines: `65`, `102`, `117`, `436`, `444`.

```suggestion
                    target={`facetime://${p.value.replace(/[^+\d]/g, "")}`}
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +18 to +32
"commands": [
{
"name": "quick-contact-actions",
"title": "Quick Contact Actions",
"description": "Search contacts and launch calls, messages, and emails instantly.",
"mode": "view",
"arguments": [
{
"name": "contact",
"placeholder": "Contact name",
"required": false,
"type": "text"
}
]
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Missing metadata/ folder with store screenshots

The extension defines a view-mode command but no metadata/ folder with Raycast-styled screenshots has been included. This folder is required before an extension can be published to the Raycast Store.

Please add a metadata/ directory containing at least one screenshot of the extension in use (PNG, 2880×1800px recommended). See the Raycast docs on screenshots for formatting requirements.

Rule Used: What: Extensions with view-type commands must incl... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/quick-contact-actions/package.json
Line: 18-32

Comment:
**Missing `metadata/` folder with store screenshots**

The extension defines a `view`-mode command but no `metadata/` folder with Raycast-styled screenshots has been included. This folder is required before an extension can be published to the Raycast Store.

Please add a `metadata/` directory containing at least one screenshot of the extension in use (PNG, 2880×1800px recommended). See the [Raycast docs on screenshots](https://developers.raycast.com/basics/prepare-an-extension-for-store#screenshots) for formatting requirements.

**Rule Used:** What: Extensions with view-type commands must incl... ([source](https://app.greptile.com/review/custom-context?memory=87059ac1-c601-487f-9f1c-bce8a3cb6209))

How can I resolve this? If you propose a fix, please make it concise.

await LocalStorage.setItem(FREQ_KEY, JSON.stringify(freq));
}

export default function Command(props: { arguments: { contact?: string } }) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Manually typed Arguments interface

The Arguments type for command props should not be defined manually — it is auto-generated in raycast-env.d.ts when the extension runs. Use LaunchProps with the generated type instead:

Suggested change
export default function Command(props: { arguments: { contact?: string } }) {
export default function Command(props: LaunchProps<{ arguments: Arguments.QuickContactActions }>) {

You'll also need to add LaunchProps to the @raycast/api import at the top of the file.

Rule Used: What: Don't manually define Preferences for `get... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/quick-contact-actions/src/quick-contact-actions.tsx
Line: 269

Comment:
**Manually typed Arguments interface**

The `Arguments` type for command props should not be defined manually — it is auto-generated in `raycast-env.d.ts` when the extension runs. Use `LaunchProps` with the generated type instead:

```suggestion
export default function Command(props: LaunchProps<{ arguments: Arguments.QuickContactActions }>) {
```

You'll also need to add `LaunchProps` to the `@raycast/api` import at the top of the file.

**Rule Used:** What: Don't manually define `Preferences` for `get... ([source](https://app.greptile.com/review/custom-context?memory=d93fc9fb-a45d-4479-a6a4-b1b4af98ebc8))

How can I resolve this? If you propose a fix, please make it concise.

@ShixAJ ShixAJ marked this pull request as draft March 28, 2026 15:28
@ShixAJ ShixAJ marked this pull request as ready for review March 28, 2026 22:33
Comment on lines +71 to +72

do {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 AppleScript injection via unescaped newlines in contact name

The Swift code escapes double quotes in the contact name before embedding it in the AppleScript string, but does not escape newline characters (\n). A contact whose givenName or familyName contains a literal newline (which CNContactStore does not prohibit) would break out of the AppleScript string and allow arbitrary AppleScript injection.

At minimum, newlines should also be stripped or escaped:

let escaped = name
    .replacingOccurrences(of: "\\", with: "\\\\")
    .replacingOccurrences(of: "\"", with: "\\\"")
    .replacingOccurrences(of: "\n", with: " ")
    .replacingOccurrences(of: "\r", with: " ")
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/quick-contact-actions/assets/get-contacts.swift
Line: 71-72

Comment:
**AppleScript injection via unescaped newlines in contact name**

The Swift code escapes double quotes in the contact name before embedding it in the AppleScript string, but does not escape newline characters (`\n`). A contact whose `givenName` or `familyName` contains a literal newline (which `CNContactStore` does not prohibit) would break out of the AppleScript string and allow arbitrary AppleScript injection.

At minimum, newlines should also be stripped or escaped:
```swift
let escaped = name
    .replacingOccurrences(of: "\\", with: "\\\\")
    .replacingOccurrences(of: "\"", with: "\\\"")
    .replacingOccurrences(of: "\n", with: " ")
    .replacingOccurrences(of: "\r", with: " ")
```

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new extension Label for PRs with new extensions platform: macOS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants