Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions extensions/apple-notes/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Apple Notes Changelog

## [AI Extension Improvements] - {PR_MERGE_DATE}

- Add `delete-note`, `restore-note`, `move-note`, `append-to-note`, and `list-folders` AI tools
- Add tag filtering to the `search-notes` AI tool
- Let the "AI Note" command append AI-generated content to an existing note via a new optional `note` argument

## [Bug Fixes] - 2026-06-07

- Fix tags not rendering in note detail view
Expand Down
185 changes: 181 additions & 4 deletions extensions/apple-notes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"andreaselia",
"fhf1121",
"charlesharries",
"janosorcsik"
"janosorcsik",
"bitforger"
],
"pastContributors": [
"thomaslombart"
Expand Down Expand Up @@ -72,7 +73,7 @@
"name": "ai",
"title": "AI Note",
"subtitle": "Apple Notes",
"description": "Create a new note filled with AI in your Apple notes.",
"description": "Create a new note filled with AI in your Apple notes, or add to an existing one.",
"mode": "no-view",
"arguments": [
{
Expand All @@ -85,6 +86,11 @@
"name": "instructions",
"type": "text",
"placeholder": "Additional instructions"
},
{
"name": "note",
"type": "text",
"placeholder": "Existing note (optional)"
}
]
},
Expand Down Expand Up @@ -150,10 +156,35 @@
"name": "update-note",
"title": "Update Note",
"description": "Update the content of a specific note in Apple Notes."
},
{
"name": "append-to-note",
"title": "Append to Note",
"description": "Add content to the end of a specific note in Apple Notes, keeping its existing content."
},
{
"name": "delete-note",
"title": "Delete Note",
"description": "Delete a specific note in Apple Notes."
},
{
"name": "restore-note",
"title": "Restore Note",
"description": "Restore a previously deleted note in Apple Notes."
},
{
"name": "move-note",
"title": "Move Note",
"description": "Move a specific note to a different folder in Apple Notes."
},
{
"name": "list-folders",
"title": "List Folders",
"description": "List the available folders in Apple Notes."
}
],
"ai": {
"instructions": "- Always format note titles as Markdown links using the note's URL.\\n- If you can't access the Apple Notes database, tell the user he needs to give Raycast the full disk access permission in the system settings. Be detailed about the steps.\\n- When calling get-note-content or update-note from search-notes results, use the note's id field and not UUID.",
"instructions": "- Always format note titles as Markdown links using the note's URL.\\n- If you can't access the Apple Notes database, tell the user he needs to give Raycast the full disk access permission in the system settings. Be detailed about the steps.\\n- When calling get-note-content, update-note, append-to-note, delete-note, or move-note from search-notes results, use the note's id field and not UUID.\\n- Use update-note to replace a note's whole content, and append-to-note to add new content while keeping what's already there.\\n- Before calling move-note, call list-folders to find a valid folder name; move-note will fail if the folder doesn't exist.\\n- delete-note is destructive and asks the user to confirm; if a note was deleted by mistake, restore-note can bring it back.",
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
"evals": [
{
"input": "@apple-notes what's on my grocery list",
Expand Down Expand Up @@ -363,6 +394,152 @@
}
}
]
},
{
"input": "@apple-notes delete my grocery list note",
"mocks": {
"search-notes": [
{
"id": "grocery-list",
"folder": "Home",
"modifiedAt": "2025-01-21 13:15:50",
"snippet": "- Bananas - Carrots",
"title": "Grocery list"
}
],
"delete-note": "Successfully called \"delete-note\""
},
"expected": [
{
"callsTool": "search-notes"
},
{
"callsTool": {
"name": "delete-note",
"arguments": {
"noteId": "grocery-list"
}
}
}
]
},
{
"input": "@apple-notes what folders do I have in Apple Notes?",
"mocks": {
"list-folders": [
{
"account": "iCloud",
"folder": "Notes"
},
{
"account": "iCloud",
"folder": "Work"
},
{
"account": "iCloud",
"folder": "Recipes"
}
]
},
"expected": [
{
"callsTool": "list-folders"
},
{
"meetsCriteria": "Returns a list of 3 folders."
}
]
},
{
"input": "@apple-notes move my grocery list note to the Work folder",
"mocks": {
"search-notes": [
{
"id": "grocery-list",
"folder": "Home",
"modifiedAt": "2025-01-21 13:15:50",
"snippet": "- Bananas - Carrots",
"title": "Grocery list"
}
],
"list-folders": [
{
"account": "iCloud",
"folder": "Home"
},
{
"account": "iCloud",
"folder": "Work"
}
],
"move-note": "Successfully called \"move-note\""
},
"expected": [
{
"callsTool": "search-notes"
},
{
"callsTool": {
"name": "move-note",
"arguments": {
"noteId": "grocery-list",
"folderName": "Work"
}
}
}
]
},
{
"input": "@apple-notes add milk to my grocery list without removing what's already there",
"mocks": {
"search-notes": [
{
"id": "grocery-list",
"folder": "Home",
"modifiedAt": "2025-01-21 13:15:50",
"snippet": "- Bananas - Carrots",
"title": "Grocery list"
}
],
"append-to-note": "Successfully called \"append-to-note\""
},
"expected": [
{
"callsTool": "search-notes"
},
{
"callsTool": {
"name": "append-to-note",
"arguments": {
"noteId": "grocery-list"
}
}
}
]
},
{
"input": "@apple-notes find my work notes tagged urgent",
"mocks": {
"search-notes": [
{
"id": "project-milestones",
"folder": "Work",
"modifiedAt": "2025-01-19 16:45:22",
"snippet": "- Design review - User testing - Launch prep",
"title": "Q1 Project Milestones"
}
]
},
"expected": [
{
"callsTool": {
"name": "search-notes",
"arguments": {
"tags": "urgent"
}
}
}
]
}
]
},
Expand Down Expand Up @@ -483,4 +660,4 @@
"platforms": [
"macOS"
]
}
}
36 changes: 30 additions & 6 deletions extensions/apple-notes/src/ai.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
import { AI, closeMainWindow, LaunchProps, showToast, Toast } from "@raycast/api";
import { showFailureToast } from "@raycast/utils";

import { createNote } from "./api/applescript";
import { appendNoteBody, createNote } from "./api/applescript";
import { getNotes } from "./api/getNotes";

export default async (props: LaunchProps<{ arguments: Arguments.Ai }>) => {
await closeMainWindow();

await showToast({ style: Toast.Style.Animated, title: "Creating a note" });

const text = props.fallbackText || props.arguments.text;

const instructions = props.arguments.instructions;
const noteQuery = props.arguments.note?.trim();

let targetNote: Awaited<ReturnType<typeof getNotes>>[number] | undefined;
if (noteQuery) {
await showToast({ style: Toast.Style.Animated, title: "Looking for note" });
const matches = await getNotes(50, [], noteQuery);
targetNote = matches.find((note) => note.title.toLowerCase() === noteQuery.toLowerCase()) ?? matches[0];
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
if (!targetNote) {
await showFailureToast(new Error(`No note matching "${noteQuery}" was found.`), {
title: "Could not find note",
});
return;
}
}

await showToast({
style: Toast.Style.Animated,
title: targetNote ? "Adding to note" : "Creating a note",
});

try {
const result = await AI.ask(
Expand All @@ -25,8 +42,15 @@ Follow these instructions:
${instructions ? `- ${instructions}` : ""}
`,
);
await createNote(result);

if (targetNote) {
await appendNoteBody(targetNote.id, result);
} else {
await createNote(result);
}
} catch (error) {
await showFailureToast(error, { title: "Could not create a new note." });
await showFailureToast(error, {
title: targetNote ? "Could not add to the note." : "Could not create a new note.",
});
}
};
60 changes: 60 additions & 0 deletions extensions/apple-notes/src/api/applescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,63 @@ export async function getSelectedNote() {
end tell
`);
}

export async function appendNoteBody(id: string, content: string) {
return runAppleScript(
`
tell application "Notes"
set theNote to note id "${escapeDoubleQuotes(id)}"
set body of theNote to (body of theNote) & "${escapeDoubleQuotes(content)}"
end tell
`,
{ timeout: 30_000 },
);
}

export async function moveNoteToFolder(id: string, folderName: string, accountName?: string) {
const escapedFolderName = escapeDoubleQuotes(folderName);
// Without an explicit account, search every account for a matching folder name.
const findFolder = accountName
? `set theFolder to folder "${escapedFolderName}" of account "${escapeDoubleQuotes(accountName)}"`
: `
set theFolder to missing value
repeat with acc in accounts
try
set theFolder to folder "${escapedFolderName}" of acc
exit repeat
end try
end repeat
if theFolder is missing value then error "Folder \\"${escapedFolderName}\\" not found"
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
`;

return runAppleScript(
`
tell application "Notes"
set theNote to note id "${escapeDoubleQuotes(id)}"
${findFolder}
move theNote to theFolder
end tell
`,
{ timeout: 30_000 },
);
}

export async function getFolders() {
return runAppleScript(
`
tell application "Notes"
set output to {}
repeat with acc in accounts
repeat with fld in folders of acc
copy ((name of acc) & "|" & (name of fld)) to end of output
end repeat
end repeat
set AppleScript's text item delimiters to linefeed
set resultText to output as text
set AppleScript's text item delimiters to ""
return resultText
end tell
`,
{ timeout: 30_000 },
);
}
4 changes: 4 additions & 0 deletions extensions/apple-notes/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export function getOpenNoteURL(uuid: string) {
}

export async function resolveAppleNoteId(noteId: string): Promise<string> {
if (!noteId) {
throw new Error('A noteId is required. Use the "id" field returned by search-notes.');
}

if (noteId.startsWith("x-coredata://")) {
return noteId;
}
Expand Down
21 changes: 21 additions & 0 deletions extensions/apple-notes/src/tools/append-to-note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { appendNoteBody } from "../api/applescript";
import { resolveAppleNoteId } from "../helpers";

type Input = {
/** The note identifier. Use the "id" value from search-notes when possible. */
noteId: string;
/**
* The content to append to the note, formatted as HTML, so that it can be pasted into Apple Notes.
*
* - Don't repeat the existing content of the note, only provide the new content to add.
* - Use the same language as the existing note.
* - Break the content into paragraphs with line breaks.
* - Don't use Markdown links (e.g. [Link](https://example.com)), use HTML links (e.g. <a href="https://example.com">Link</a>).
*/
content: string;
};

export default async function (input: Input) {
const noteId = await resolveAppleNoteId(input.noteId);
return appendNoteBody(noteId, input.content);
}
Loading
Loading