iris-chatbot 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/bin/iris.mjs +267 -0
- package/package.json +61 -0
- package/template/LICENSE +21 -0
- package/template/README.md +49 -0
- package/template/eslint.config.mjs +18 -0
- package/template/next.config.ts +7 -0
- package/template/package-lock.json +9193 -0
- package/template/package.json +46 -0
- package/template/postcss.config.mjs +7 -0
- package/template/public/file.svg +1 -0
- package/template/public/globe.svg +1 -0
- package/template/public/next.svg +1 -0
- package/template/public/vercel.svg +1 -0
- package/template/public/window.svg +1 -0
- package/template/src/app/api/chat/route.ts +2445 -0
- package/template/src/app/api/connections/models/route.ts +255 -0
- package/template/src/app/api/connections/test/route.ts +124 -0
- package/template/src/app/api/local-sync/route.ts +74 -0
- package/template/src/app/api/tool-approval/route.ts +47 -0
- package/template/src/app/favicon.ico +0 -0
- package/template/src/app/globals.css +808 -0
- package/template/src/app/layout.tsx +74 -0
- package/template/src/app/page.tsx +444 -0
- package/template/src/components/ChatView.tsx +1537 -0
- package/template/src/components/Composer.tsx +160 -0
- package/template/src/components/MapView.tsx +244 -0
- package/template/src/components/MessageCard.tsx +955 -0
- package/template/src/components/SearchModal.tsx +72 -0
- package/template/src/components/SettingsModal.tsx +1257 -0
- package/template/src/components/Sidebar.tsx +153 -0
- package/template/src/components/TopBar.tsx +164 -0
- package/template/src/lib/connections.ts +275 -0
- package/template/src/lib/data.ts +324 -0
- package/template/src/lib/db.ts +49 -0
- package/template/src/lib/hooks.ts +76 -0
- package/template/src/lib/local-sync.ts +192 -0
- package/template/src/lib/memory.ts +695 -0
- package/template/src/lib/model-presets.ts +251 -0
- package/template/src/lib/store.ts +36 -0
- package/template/src/lib/tooling/approvals.ts +78 -0
- package/template/src/lib/tooling/providers/anthropic.ts +155 -0
- package/template/src/lib/tooling/providers/ollama.ts +73 -0
- package/template/src/lib/tooling/providers/openai.ts +267 -0
- package/template/src/lib/tooling/providers/openai_compatible.ts +16 -0
- package/template/src/lib/tooling/providers/types.ts +44 -0
- package/template/src/lib/tooling/registry.ts +103 -0
- package/template/src/lib/tooling/runtime.ts +189 -0
- package/template/src/lib/tooling/safety.ts +165 -0
- package/template/src/lib/tooling/tools/apps.ts +108 -0
- package/template/src/lib/tooling/tools/apps_plus.ts +153 -0
- package/template/src/lib/tooling/tools/communication.ts +883 -0
- package/template/src/lib/tooling/tools/files.ts +395 -0
- package/template/src/lib/tooling/tools/music.ts +988 -0
- package/template/src/lib/tooling/tools/notes.ts +461 -0
- package/template/src/lib/tooling/tools/notes_plus.ts +294 -0
- package/template/src/lib/tooling/tools/numbers.ts +175 -0
- package/template/src/lib/tooling/tools/schedule.ts +579 -0
- package/template/src/lib/tooling/tools/system.ts +142 -0
- package/template/src/lib/tooling/tools/web.ts +212 -0
- package/template/src/lib/tooling/tools/workflow.ts +218 -0
- package/template/src/lib/tooling/types.ts +27 -0
- package/template/src/lib/types.ts +309 -0
- package/template/src/lib/utils.ts +108 -0
- package/template/tsconfig.json +34 -0
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
import { ensureMacOS } from "../safety";
|
|
2
|
+
import { runCommandSafe } from "../runtime";
|
|
3
|
+
import type { ToolDefinition, ToolExecutionContext } from "../types";
|
|
4
|
+
|
|
5
|
+
type CalendarCreateInput = {
|
|
6
|
+
title?: string;
|
|
7
|
+
start?: string;
|
|
8
|
+
end?: string;
|
|
9
|
+
location?: string;
|
|
10
|
+
notes?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type CalendarListInput = {
|
|
14
|
+
from?: string;
|
|
15
|
+
to?: string;
|
|
16
|
+
limit?: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type ReminderCreateInput = {
|
|
20
|
+
title?: string;
|
|
21
|
+
list?: string;
|
|
22
|
+
due?: string;
|
|
23
|
+
notes?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type ReminderListInput = {
|
|
27
|
+
list?: string;
|
|
28
|
+
status?: "open" | "completed" | "all";
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type DateTimeParts = {
|
|
32
|
+
year: number;
|
|
33
|
+
month: number;
|
|
34
|
+
day: number;
|
|
35
|
+
hour: number;
|
|
36
|
+
minute: number;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const APPLESCRIPT_TIMEOUT_MS = 30_000;
|
|
40
|
+
const WEEKDAY_TO_INDEX: Record<string, number> = {
|
|
41
|
+
sunday: 0,
|
|
42
|
+
monday: 1,
|
|
43
|
+
tuesday: 2,
|
|
44
|
+
wednesday: 3,
|
|
45
|
+
thursday: 4,
|
|
46
|
+
friday: 5,
|
|
47
|
+
saturday: 6,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function asObject(input: unknown): Record<string, unknown> {
|
|
51
|
+
if (!input || typeof input !== "object") {
|
|
52
|
+
throw new Error("Tool input must be an object.");
|
|
53
|
+
}
|
|
54
|
+
return input as Record<string, unknown>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function asString(input: unknown, field: string): string {
|
|
58
|
+
if (typeof input !== "string" || !input.trim()) {
|
|
59
|
+
throw new Error(`Missing required string field: ${field}`);
|
|
60
|
+
}
|
|
61
|
+
return input.trim();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function toDueParts(date: Date): DateTimeParts {
|
|
65
|
+
return {
|
|
66
|
+
year: date.getFullYear(),
|
|
67
|
+
month: date.getMonth() + 1,
|
|
68
|
+
day: date.getDate(),
|
|
69
|
+
hour: date.getHours(),
|
|
70
|
+
minute: date.getMinutes(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseTimeOfDay(input: string): { hour: number; minute: number } | null {
|
|
75
|
+
const twelveHourMatch = input.match(/\b(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)\b/i);
|
|
76
|
+
if (twelveHourMatch) {
|
|
77
|
+
const hoursRaw = Number(twelveHourMatch[1]);
|
|
78
|
+
const minute = Number(twelveHourMatch[2] ?? "0");
|
|
79
|
+
if (!Number.isFinite(hoursRaw) || !Number.isFinite(minute) || minute < 0 || minute > 59) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
let hour = hoursRaw % 12;
|
|
83
|
+
if (twelveHourMatch[3].toLowerCase() === "pm") {
|
|
84
|
+
hour += 12;
|
|
85
|
+
}
|
|
86
|
+
return { hour, minute };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const twentyFourHourMatch = input.match(/\b(?:at\s+)?([01]?\d|2[0-3]):([0-5]\d)\b/);
|
|
90
|
+
if (twentyFourHourMatch) {
|
|
91
|
+
return {
|
|
92
|
+
hour: Number(twentyFourHourMatch[1]),
|
|
93
|
+
minute: Number(twentyFourHourMatch[2]),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function resolveRelativeDate(input: string, now: Date): Date | null {
|
|
101
|
+
const lower = input.toLowerCase().replace(/\bthis\s+upcoming\s+/g, "upcoming ");
|
|
102
|
+
const base = new Date(now);
|
|
103
|
+
|
|
104
|
+
if (/\btomorrow\b/.test(lower)) {
|
|
105
|
+
base.setDate(base.getDate() + 1);
|
|
106
|
+
return base;
|
|
107
|
+
}
|
|
108
|
+
if (/\btoday\b/.test(lower)) {
|
|
109
|
+
return base;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const weekdayMatch = lower.match(
|
|
113
|
+
/\b(?:(this|next|upcoming)\s+)?(sunday|monday|tuesday|wednesday|thursday|friday|saturday)\b/,
|
|
114
|
+
);
|
|
115
|
+
if (!weekdayMatch?.[2]) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const modifier = weekdayMatch[1];
|
|
120
|
+
const targetDay = WEEKDAY_TO_INDEX[weekdayMatch[2]];
|
|
121
|
+
if (!Number.isFinite(targetDay)) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let delta = (targetDay - base.getDay() + 7) % 7;
|
|
126
|
+
if (modifier === "next") {
|
|
127
|
+
if (delta === 0) {
|
|
128
|
+
delta = 7;
|
|
129
|
+
}
|
|
130
|
+
} else if (modifier !== "this" && delta === 0) {
|
|
131
|
+
delta = 7;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
base.setDate(base.getDate() + delta);
|
|
135
|
+
return base;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function parseDateTimeParts(
|
|
139
|
+
input: string,
|
|
140
|
+
options?: { defaultHour?: number; defaultMinute?: number },
|
|
141
|
+
): DateTimeParts | null {
|
|
142
|
+
const defaultHour = options?.defaultHour ?? 9;
|
|
143
|
+
const defaultMinute = options?.defaultMinute ?? 0;
|
|
144
|
+
const normalized = input.trim();
|
|
145
|
+
if (!normalized) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const isoMatch = input.match(
|
|
150
|
+
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::\d{2})?$/,
|
|
151
|
+
);
|
|
152
|
+
if (isoMatch) {
|
|
153
|
+
return {
|
|
154
|
+
year: Number(isoMatch[1]),
|
|
155
|
+
month: Number(isoMatch[2]),
|
|
156
|
+
day: Number(isoMatch[3]),
|
|
157
|
+
hour: Number(isoMatch[4]),
|
|
158
|
+
minute: Number(isoMatch[5]),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const dateOnlyMatch = normalized.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
163
|
+
if (dateOnlyMatch) {
|
|
164
|
+
return {
|
|
165
|
+
year: Number(dateOnlyMatch[1]),
|
|
166
|
+
month: Number(dateOnlyMatch[2]),
|
|
167
|
+
day: Number(dateOnlyMatch[3]),
|
|
168
|
+
hour: defaultHour,
|
|
169
|
+
minute: defaultMinute,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const isoOrSpaceMatch = normalized.match(
|
|
174
|
+
/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::\d{2})?(?:Z)?$/,
|
|
175
|
+
);
|
|
176
|
+
if (isoOrSpaceMatch) {
|
|
177
|
+
return {
|
|
178
|
+
year: Number(isoOrSpaceMatch[1]),
|
|
179
|
+
month: Number(isoOrSpaceMatch[2]),
|
|
180
|
+
day: Number(isoOrSpaceMatch[3]),
|
|
181
|
+
hour: Number(isoOrSpaceMatch[4]),
|
|
182
|
+
minute: Number(isoOrSpaceMatch[5]),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const parsed = new Date(normalized);
|
|
187
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
188
|
+
return toDueParts(parsed);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const now = new Date();
|
|
192
|
+
const timeOfDay = parseTimeOfDay(normalized);
|
|
193
|
+
const relativeDate = resolveRelativeDate(normalized, now);
|
|
194
|
+
|
|
195
|
+
if (timeOfDay && relativeDate) {
|
|
196
|
+
relativeDate.setHours(timeOfDay.hour, timeOfDay.minute, 0, 0);
|
|
197
|
+
return toDueParts(relativeDate);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (timeOfDay) {
|
|
201
|
+
const candidate = new Date(now);
|
|
202
|
+
candidate.setHours(timeOfDay.hour, timeOfDay.minute, 0, 0);
|
|
203
|
+
if (candidate.getTime() < now.getTime() - 60_000) {
|
|
204
|
+
candidate.setDate(candidate.getDate() + 1);
|
|
205
|
+
}
|
|
206
|
+
return toDueParts(candidate);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (relativeDate) {
|
|
210
|
+
relativeDate.setHours(defaultHour, defaultMinute, 0, 0);
|
|
211
|
+
return toDueParts(relativeDate);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function addMinutes(parts: DateTimeParts, minutesToAdd: number): DateTimeParts {
|
|
218
|
+
const value = new Date(
|
|
219
|
+
parts.year,
|
|
220
|
+
parts.month - 1,
|
|
221
|
+
parts.day,
|
|
222
|
+
parts.hour,
|
|
223
|
+
parts.minute,
|
|
224
|
+
0,
|
|
225
|
+
0,
|
|
226
|
+
);
|
|
227
|
+
value.setMinutes(value.getMinutes() + minutesToAdd);
|
|
228
|
+
return toDueParts(value);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function runAppleScript(script: string, args: string[] = [], signal?: AbortSignal) {
|
|
232
|
+
const { stdout } = await runCommandSafe({
|
|
233
|
+
command: "osascript",
|
|
234
|
+
args: ["-e", script, ...args],
|
|
235
|
+
signal,
|
|
236
|
+
timeoutMs: APPLESCRIPT_TIMEOUT_MS,
|
|
237
|
+
});
|
|
238
|
+
return stdout;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function runCalendarCreateEvent(input: unknown, context: ToolExecutionContext) {
|
|
242
|
+
ensureMacOS("Calendar automation");
|
|
243
|
+
const payload = asObject(input) as CalendarCreateInput;
|
|
244
|
+
const title = asString(payload.title, "title");
|
|
245
|
+
const start = asString(payload.start, "start");
|
|
246
|
+
const endInput = typeof payload.end === "string" && payload.end.trim() ? payload.end.trim() : "";
|
|
247
|
+
const end = endInput || start;
|
|
248
|
+
const location = typeof payload.location === "string" ? payload.location.trim() : "";
|
|
249
|
+
const notes = typeof payload.notes === "string" ? payload.notes.trim() : "";
|
|
250
|
+
const startParts = parseDateTimeParts(start, { defaultHour: 9, defaultMinute: 0 });
|
|
251
|
+
if (!startParts) {
|
|
252
|
+
throw new Error(`Invalid start date/time: ${start}`);
|
|
253
|
+
}
|
|
254
|
+
const endParts = endInput
|
|
255
|
+
? parseDateTimeParts(endInput, {
|
|
256
|
+
defaultHour: startParts.hour,
|
|
257
|
+
defaultMinute: startParts.minute,
|
|
258
|
+
})
|
|
259
|
+
: addMinutes(startParts, 60);
|
|
260
|
+
if (!endParts) {
|
|
261
|
+
throw new Error(`Invalid end date/time: ${endInput}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (context.localTools.dryRun) {
|
|
265
|
+
return {
|
|
266
|
+
dryRun: true,
|
|
267
|
+
action: "calendar_create_event",
|
|
268
|
+
title,
|
|
269
|
+
start,
|
|
270
|
+
end,
|
|
271
|
+
startParts,
|
|
272
|
+
endParts,
|
|
273
|
+
location,
|
|
274
|
+
notes,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const script =
|
|
279
|
+
'on run argv\n' +
|
|
280
|
+
'set eventTitle to item 1 of argv\n' +
|
|
281
|
+
'set startYear to (item 2 of argv) as integer\n' +
|
|
282
|
+
'set startMonth to (item 3 of argv) as integer\n' +
|
|
283
|
+
'set startDay to (item 4 of argv) as integer\n' +
|
|
284
|
+
'set startHour to (item 5 of argv) as integer\n' +
|
|
285
|
+
'set startMinute to (item 6 of argv) as integer\n' +
|
|
286
|
+
'set endYear to (item 7 of argv) as integer\n' +
|
|
287
|
+
'set endMonth to (item 8 of argv) as integer\n' +
|
|
288
|
+
'set endDay to (item 9 of argv) as integer\n' +
|
|
289
|
+
'set endHour to (item 10 of argv) as integer\n' +
|
|
290
|
+
'set endMinute to (item 11 of argv) as integer\n' +
|
|
291
|
+
'set eventLocation to item 12 of argv\n' +
|
|
292
|
+
'set eventNotes to item 13 of argv\n' +
|
|
293
|
+
'tell application "Calendar"\n' +
|
|
294
|
+
'set targetCalendar to missing value\n' +
|
|
295
|
+
'try\n' +
|
|
296
|
+
'set targetCalendar to calendar "Home"\n' +
|
|
297
|
+
'end try\n' +
|
|
298
|
+
'if targetCalendar is missing value then\n' +
|
|
299
|
+
'try\n' +
|
|
300
|
+
'set targetCalendar to first calendar\n' +
|
|
301
|
+
'end try\n' +
|
|
302
|
+
'end if\n' +
|
|
303
|
+
'if targetCalendar is missing value then error "No calendar is available."\n' +
|
|
304
|
+
'set startDate to current date\n' +
|
|
305
|
+
'set year of startDate to startYear\n' +
|
|
306
|
+
'set month of startDate to startMonth\n' +
|
|
307
|
+
'set day of startDate to startDay\n' +
|
|
308
|
+
'set time of startDate to ((startHour * hours) + (startMinute * minutes))\n' +
|
|
309
|
+
'set endDate to current date\n' +
|
|
310
|
+
'set year of endDate to endYear\n' +
|
|
311
|
+
'set month of endDate to endMonth\n' +
|
|
312
|
+
'set day of endDate to endDay\n' +
|
|
313
|
+
'set time of endDate to ((endHour * hours) + (endMinute * minutes))\n' +
|
|
314
|
+
'tell targetCalendar\n' +
|
|
315
|
+
'set createdEvent to make new event with properties {summary:eventTitle, start date:startDate, end date:endDate, location:eventLocation, description:eventNotes}\n' +
|
|
316
|
+
"end tell\n" +
|
|
317
|
+
'set calendarName to (name of targetCalendar as text)\n' +
|
|
318
|
+
"end tell\n" +
|
|
319
|
+
'return "created" & tab & calendarName\n' +
|
|
320
|
+
"end run";
|
|
321
|
+
const output = await runAppleScript(
|
|
322
|
+
script,
|
|
323
|
+
[
|
|
324
|
+
title,
|
|
325
|
+
String(startParts.year),
|
|
326
|
+
String(startParts.month),
|
|
327
|
+
String(startParts.day),
|
|
328
|
+
String(startParts.hour),
|
|
329
|
+
String(startParts.minute),
|
|
330
|
+
String(endParts.year),
|
|
331
|
+
String(endParts.month),
|
|
332
|
+
String(endParts.day),
|
|
333
|
+
String(endParts.hour),
|
|
334
|
+
String(endParts.minute),
|
|
335
|
+
location,
|
|
336
|
+
notes,
|
|
337
|
+
],
|
|
338
|
+
context.signal,
|
|
339
|
+
);
|
|
340
|
+
const [, calendarName] = output.split("\t");
|
|
341
|
+
return {
|
|
342
|
+
created: true,
|
|
343
|
+
title,
|
|
344
|
+
start,
|
|
345
|
+
end,
|
|
346
|
+
startResolved: `${startParts.year}-${String(startParts.month).padStart(2, "0")}-${String(
|
|
347
|
+
startParts.day,
|
|
348
|
+
).padStart(2, "0")}T${String(startParts.hour).padStart(2, "0")}:${String(
|
|
349
|
+
startParts.minute,
|
|
350
|
+
).padStart(2, "0")}:00`,
|
|
351
|
+
endResolved: `${endParts.year}-${String(endParts.month).padStart(2, "0")}-${String(
|
|
352
|
+
endParts.day,
|
|
353
|
+
).padStart(2, "0")}T${String(endParts.hour).padStart(2, "0")}:${String(
|
|
354
|
+
endParts.minute,
|
|
355
|
+
).padStart(2, "0")}:00`,
|
|
356
|
+
calendar: calendarName?.trim() || null,
|
|
357
|
+
location,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function runCalendarListEvents(input: unknown, context: ToolExecutionContext) {
|
|
362
|
+
ensureMacOS("Calendar automation");
|
|
363
|
+
const payload = asObject(input) as CalendarListInput;
|
|
364
|
+
const from = typeof payload.from === "string" && payload.from.trim() ? payload.from.trim() : "today";
|
|
365
|
+
const to = typeof payload.to === "string" && payload.to.trim() ? payload.to.trim() : "tomorrow";
|
|
366
|
+
const limit =
|
|
367
|
+
typeof payload.limit === "number" && Number.isFinite(payload.limit)
|
|
368
|
+
? Math.max(1, Math.min(50, Math.floor(payload.limit)))
|
|
369
|
+
: 10;
|
|
370
|
+
|
|
371
|
+
const script =
|
|
372
|
+
'on run argv\n' +
|
|
373
|
+
'set fromText to item 1 of argv\n' +
|
|
374
|
+
'set toText to item 2 of argv\n' +
|
|
375
|
+
'set maxCount to (item 3 of argv) as integer\n' +
|
|
376
|
+
"set outText to \"\"\n" +
|
|
377
|
+
"set countItems to 0\n" +
|
|
378
|
+
'tell application "Calendar"\n' +
|
|
379
|
+
"set fromDate to date fromText\n" +
|
|
380
|
+
"set toDate to date toText\n" +
|
|
381
|
+
"repeat with cal in calendars\n" +
|
|
382
|
+
"repeat with e in (every event of cal whose start date is greater than or equal to fromDate and start date is less than or equal to toDate)\n" +
|
|
383
|
+
'set outText to outText & (summary of e as text) & tab & ((start date of e) as text) & tab & (name of cal as text) & linefeed\n' +
|
|
384
|
+
"set countItems to countItems + 1\n" +
|
|
385
|
+
"if countItems is greater than or equal to maxCount then return outText\n" +
|
|
386
|
+
"end repeat\n" +
|
|
387
|
+
"end repeat\n" +
|
|
388
|
+
"end tell\n" +
|
|
389
|
+
"return outText\n" +
|
|
390
|
+
"end run";
|
|
391
|
+
const output = await runAppleScript(script, [from, to, String(limit)], context.signal);
|
|
392
|
+
const entries = output
|
|
393
|
+
.split("\n")
|
|
394
|
+
.map((line) => line.trim())
|
|
395
|
+
.filter(Boolean)
|
|
396
|
+
.map((line) => {
|
|
397
|
+
const [title, startAt, calendar] = line.split("\t");
|
|
398
|
+
return { title, startAt, calendar };
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
return { from, to, count: entries.length, entries };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function runReminderCreate(input: unknown, context: ToolExecutionContext) {
|
|
405
|
+
ensureMacOS("Reminders automation");
|
|
406
|
+
const payload = asObject(input) as ReminderCreateInput;
|
|
407
|
+
const title = asString(payload.title, "title");
|
|
408
|
+
const list = typeof payload.list === "string" && payload.list.trim() ? payload.list.trim() : "Reminders";
|
|
409
|
+
const due = typeof payload.due === "string" ? payload.due.trim() : "";
|
|
410
|
+
const notes = typeof payload.notes === "string" ? payload.notes.trim() : "";
|
|
411
|
+
const dueParts = due ? parseDateTimeParts(due, { defaultHour: 9, defaultMinute: 0 }) : null;
|
|
412
|
+
|
|
413
|
+
if (context.localTools.dryRun) {
|
|
414
|
+
return {
|
|
415
|
+
dryRun: true,
|
|
416
|
+
action: "reminder_create",
|
|
417
|
+
title,
|
|
418
|
+
list,
|
|
419
|
+
due,
|
|
420
|
+
dueParts,
|
|
421
|
+
notes,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const script =
|
|
426
|
+
'on run argv\n' +
|
|
427
|
+
'set itemTitle to item 1 of argv\n' +
|
|
428
|
+
'set listName to item 2 of argv\n' +
|
|
429
|
+
'set dueFlag to item 3 of argv\n' +
|
|
430
|
+
'set dueYear to (item 4 of argv) as integer\n' +
|
|
431
|
+
'set dueMonth to (item 5 of argv) as integer\n' +
|
|
432
|
+
'set dueDay to (item 6 of argv) as integer\n' +
|
|
433
|
+
'set dueHour to (item 7 of argv) as integer\n' +
|
|
434
|
+
'set dueMinute to (item 8 of argv) as integer\n' +
|
|
435
|
+
'set dueText to item 9 of argv\n' +
|
|
436
|
+
'set noteText to item 10 of argv\n' +
|
|
437
|
+
'tell application "Reminders"\n' +
|
|
438
|
+
'set targetList to list listName\n' +
|
|
439
|
+
'set newReminder to make new reminder at end of reminders of targetList with properties {name:itemTitle, body:noteText}\n' +
|
|
440
|
+
'if dueFlag is "1" then\n' +
|
|
441
|
+
'set dueDateValue to current date\n' +
|
|
442
|
+
'set year of dueDateValue to dueYear\n' +
|
|
443
|
+
'set month of dueDateValue to dueMonth\n' +
|
|
444
|
+
'set day of dueDateValue to dueDay\n' +
|
|
445
|
+
'set time of dueDateValue to ((dueHour * hours) + (dueMinute * minutes))\n' +
|
|
446
|
+
'set due date of newReminder to dueDateValue\n' +
|
|
447
|
+
'else if dueText is not "" then\n' +
|
|
448
|
+
'set due date of newReminder to date dueText\n' +
|
|
449
|
+
'end if\n' +
|
|
450
|
+
'set dueOut to ""\n' +
|
|
451
|
+
'try\n' +
|
|
452
|
+
'set dueOut to (due date of newReminder as text)\n' +
|
|
453
|
+
'end try\n' +
|
|
454
|
+
"end tell\n" +
|
|
455
|
+
'return "created" & tab & dueOut\n' +
|
|
456
|
+
"end run";
|
|
457
|
+
const scriptArgs = [
|
|
458
|
+
title,
|
|
459
|
+
list,
|
|
460
|
+
dueParts ? "1" : "0",
|
|
461
|
+
String(dueParts?.year ?? 0),
|
|
462
|
+
String(dueParts?.month ?? 0),
|
|
463
|
+
String(dueParts?.day ?? 0),
|
|
464
|
+
String(dueParts?.hour ?? 0),
|
|
465
|
+
String(dueParts?.minute ?? 0),
|
|
466
|
+
due,
|
|
467
|
+
notes,
|
|
468
|
+
];
|
|
469
|
+
const output = await runAppleScript(script, scriptArgs, context.signal);
|
|
470
|
+
const [, dueResolved] = output.split("\t");
|
|
471
|
+
return {
|
|
472
|
+
created: true,
|
|
473
|
+
title,
|
|
474
|
+
list,
|
|
475
|
+
due: due || null,
|
|
476
|
+
dueResolved: dueResolved?.trim() || null,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function runReminderList(input: unknown, context: ToolExecutionContext) {
|
|
481
|
+
ensureMacOS("Reminders automation");
|
|
482
|
+
const payload = asObject(input) as ReminderListInput;
|
|
483
|
+
const list = typeof payload.list === "string" && payload.list.trim() ? payload.list.trim() : "Reminders";
|
|
484
|
+
const status = payload.status ?? "open";
|
|
485
|
+
|
|
486
|
+
const script =
|
|
487
|
+
'on run argv\n' +
|
|
488
|
+
'set listName to item 1 of argv\n' +
|
|
489
|
+
'set stateFilter to item 2 of argv\n' +
|
|
490
|
+
'set outText to ""\n' +
|
|
491
|
+
'tell application "Reminders"\n' +
|
|
492
|
+
'set targetList to list listName\n' +
|
|
493
|
+
'repeat with r in reminders of targetList\n' +
|
|
494
|
+
'set isDone to completed of r\n' +
|
|
495
|
+
'if stateFilter is "all" or (stateFilter is "open" and isDone is false) or (stateFilter is "completed" and isDone is true) then\n' +
|
|
496
|
+
'set outText to outText & (name of r as text) & tab & (isDone as text) & linefeed\n' +
|
|
497
|
+
'end if\n' +
|
|
498
|
+
'end repeat\n' +
|
|
499
|
+
'end tell\n' +
|
|
500
|
+
'return outText\n' +
|
|
501
|
+
'end run';
|
|
502
|
+
const output = await runAppleScript(script, [list, status], context.signal);
|
|
503
|
+
const entries = output
|
|
504
|
+
.split("\n")
|
|
505
|
+
.map((line) => line.trim())
|
|
506
|
+
.filter(Boolean)
|
|
507
|
+
.map((line) => {
|
|
508
|
+
const [title, completed] = line.split("\t");
|
|
509
|
+
return { title, completed: completed === "true" };
|
|
510
|
+
});
|
|
511
|
+
return { list, status, count: entries.length, entries };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export const scheduleTools: ToolDefinition[] = [
|
|
515
|
+
{
|
|
516
|
+
name: "calendar_create_event",
|
|
517
|
+
description: "Create a Calendar event.",
|
|
518
|
+
inputSchema: {
|
|
519
|
+
type: "object",
|
|
520
|
+
required: ["title", "start"],
|
|
521
|
+
properties: {
|
|
522
|
+
title: { type: "string" },
|
|
523
|
+
start: { type: "string" },
|
|
524
|
+
end: { type: "string" },
|
|
525
|
+
location: { type: "string" },
|
|
526
|
+
notes: { type: "string" },
|
|
527
|
+
},
|
|
528
|
+
additionalProperties: false,
|
|
529
|
+
},
|
|
530
|
+
risk: "write",
|
|
531
|
+
execute: runCalendarCreateEvent,
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
name: "calendar_list_events",
|
|
535
|
+
description: "List calendar events in a time range.",
|
|
536
|
+
inputSchema: {
|
|
537
|
+
type: "object",
|
|
538
|
+
properties: {
|
|
539
|
+
from: { type: "string" },
|
|
540
|
+
to: { type: "string" },
|
|
541
|
+
limit: { type: "number" },
|
|
542
|
+
},
|
|
543
|
+
additionalProperties: false,
|
|
544
|
+
},
|
|
545
|
+
risk: "read",
|
|
546
|
+
execute: runCalendarListEvents,
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
name: "reminder_create",
|
|
550
|
+
description: "Create a reminder item.",
|
|
551
|
+
inputSchema: {
|
|
552
|
+
type: "object",
|
|
553
|
+
required: ["title"],
|
|
554
|
+
properties: {
|
|
555
|
+
title: { type: "string" },
|
|
556
|
+
list: { type: "string" },
|
|
557
|
+
due: { type: "string" },
|
|
558
|
+
notes: { type: "string" },
|
|
559
|
+
},
|
|
560
|
+
additionalProperties: false,
|
|
561
|
+
},
|
|
562
|
+
risk: "write",
|
|
563
|
+
execute: runReminderCreate,
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
name: "reminder_list",
|
|
567
|
+
description: "List reminders by status.",
|
|
568
|
+
inputSchema: {
|
|
569
|
+
type: "object",
|
|
570
|
+
properties: {
|
|
571
|
+
list: { type: "string" },
|
|
572
|
+
status: { type: "string", enum: ["open", "completed", "all"] },
|
|
573
|
+
},
|
|
574
|
+
additionalProperties: false,
|
|
575
|
+
},
|
|
576
|
+
risk: "read",
|
|
577
|
+
execute: runReminderList,
|
|
578
|
+
},
|
|
579
|
+
];
|