2345627eb8
Mac mini is on macOS 26.5.2 Mac16,10 M4 - SpeechAnalyzer available
- Calendar/Contacts/Notes/Reminders use full /System/Applications/*.app path (more reliable)
- Reminders now returns assignment {assignee, source, available} with fallback to (Name) in title
- deco.js uses deco_ha_bridge.py for clients action (more reliable than old bridge)
- Added deco_ha_bridge.py (13KB RSA/AES Deco HA client)
- Added system_get_info + system_speech_api_status (verifies SpeechAnalyzer on macOS 26+)
- Preserved codex-image, gemini-image, gemini-chrome-prompt tools
- Updated README with all 23 tools + macOS 26 note + benchmark ref
172 lines
5.1 KiB
JavaScript
172 lines
5.1 KiB
JavaScript
import { dateFromInput, runJxa } from "../apple-events.js";
|
|
|
|
const LISTS_SCRIPT = String.raw`
|
|
function run() {
|
|
const app = Application("/System/Applications/Reminders.app");
|
|
function stringValue(value) {
|
|
try {
|
|
return value === null || value === undefined ? null : String(value);
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
function accountName(list) {
|
|
try {
|
|
const container = list.container();
|
|
return stringValue(container.name ? container.name() : container);
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
return JSON.stringify(app.lists().map(function (list) {
|
|
return {
|
|
id: String(list.id()),
|
|
name: String(list.name()),
|
|
account: accountName(list),
|
|
shared: null,
|
|
assignmentMetadata: {
|
|
available: null,
|
|
note: "Apple Reminders automation does not expose shared-list participant metadata directly."
|
|
}
|
|
};
|
|
}));
|
|
}`;
|
|
|
|
const REMINDERS_LIST_SCRIPT = String.raw`
|
|
function run(argv) {
|
|
const input = JSON.parse(argv[0]);
|
|
const app = Application("/System/Applications/Reminders.app");
|
|
const lists = app.lists();
|
|
const found = [];
|
|
function stringValue(value) {
|
|
try {
|
|
return value === null || value === undefined ? null : String(value);
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
function compact(value) {
|
|
if (!value) return null;
|
|
const text = String(value).trim();
|
|
return text ? text : null;
|
|
}
|
|
function propertyValue(object, names) {
|
|
for (let i = 0; i < names.length; i++) {
|
|
try {
|
|
const getter = object[names[i]];
|
|
if (typeof getter !== "function") continue;
|
|
const value = getter.call(object);
|
|
const text = compact(stringValue(value));
|
|
if (text && !text.startsWith("[object ")) return text;
|
|
if (value && typeof value.name === "function") {
|
|
const name = compact(stringValue(value.name()));
|
|
if (name) return name;
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
return null;
|
|
}
|
|
function assignmentHint(title, notes) {
|
|
const titleMatch = title.match(/\(([^()\n]{2,80})\)\s*$/);
|
|
if (titleMatch) return { assignee: titleMatch[1].trim(), source: "title" };
|
|
|
|
const explicit = notes.match(/\b(?:assigned to|assignee)\s*:\s*([^.;,\n]{2,80})/i);
|
|
if (explicit) return { assignee: explicit[1].trim(), source: "notes" };
|
|
|
|
const captured = notes.match(/\bCaptured\s+\d{4}-\d{2}-\d{2},\s*([^.;,\n]{2,80})\s*[.;]/i);
|
|
if (captured) return { assignee: captured[1].trim(), source: "notes" };
|
|
|
|
return { assignee: null, source: null };
|
|
}
|
|
function assignmentFor(reminder, title, notes) {
|
|
const nativeAssignee = propertyValue(reminder, [
|
|
"assignedTo",
|
|
"assignee",
|
|
"assignment",
|
|
"responsiblePerson",
|
|
"principal"
|
|
]);
|
|
if (nativeAssignee) {
|
|
return {
|
|
assignee: nativeAssignee,
|
|
source: "remindersAutomation",
|
|
available: true
|
|
};
|
|
}
|
|
|
|
const hint = assignmentHint(title, notes);
|
|
return {
|
|
assignee: hint.assignee,
|
|
source: hint.source,
|
|
available: hint.assignee !== null
|
|
};
|
|
}
|
|
for (let l = 0; l < lists.length && found.length < input.limit; l++) {
|
|
const list = lists[l];
|
|
const name = String(list.name());
|
|
if (input.list && name !== input.list) continue;
|
|
const reminders = list.reminders();
|
|
for (let r = 0; r < reminders.length && found.length < input.limit; r++) {
|
|
const reminder = reminders[r];
|
|
const completed = Boolean(reminder.completed());
|
|
if (input.completed !== null && completed !== input.completed) continue;
|
|
let due = null;
|
|
try {
|
|
const value = reminder.dueDate();
|
|
due = value ? value.toISOString() : null;
|
|
} catch (_) {}
|
|
const title = String(reminder.name());
|
|
const notes = String(reminder.body() || "");
|
|
found.push({
|
|
id: String(reminder.id()),
|
|
list: name,
|
|
title: title,
|
|
completed: completed,
|
|
due: due,
|
|
notes: notes,
|
|
assignment: assignmentFor(reminder, title, notes)
|
|
});
|
|
}
|
|
}
|
|
return JSON.stringify(found);
|
|
}`;
|
|
|
|
const REMINDER_CREATE_SCRIPT = String.raw`
|
|
function run(argv) {
|
|
const input = JSON.parse(argv[0]);
|
|
const app = Application("/System/Applications/Reminders.app");
|
|
const lists = app.lists();
|
|
let destination = null;
|
|
for (let l = 0; l < lists.length; l++) {
|
|
if (!input.list || String(lists[l].name()) === input.list) {
|
|
destination = lists[l];
|
|
break;
|
|
}
|
|
}
|
|
if (!destination) throw new Error("Reminders list not found");
|
|
const properties = {name: input.title, body: input.notes || ""};
|
|
if (input.due) properties.dueDate = new Date(input.due);
|
|
const reminder = app.Reminder(properties);
|
|
destination.reminders.push(reminder);
|
|
return JSON.stringify({
|
|
id: String(reminder.id()),
|
|
list: String(destination.name()),
|
|
title: String(reminder.name())
|
|
});
|
|
}`;
|
|
|
|
export function listReminderLists() {
|
|
return runJxa(LISTS_SCRIPT);
|
|
}
|
|
|
|
export function listReminders(input) {
|
|
return runJxa(REMINDERS_LIST_SCRIPT, input);
|
|
}
|
|
|
|
export function createReminder(input) {
|
|
if (input.due) {
|
|
dateFromInput(input.due, "due");
|
|
}
|
|
return runJxa(REMINDER_CREATE_SCRIPT, input);
|
|
}
|