@madh-io/alfred-ai 0.7.1 → 0.7.2

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.
Files changed (2) hide show
  1. package/bundle/index.js +138 -14
  2. package/package.json +1 -1
package/bundle/index.js CHANGED
@@ -2035,6 +2035,29 @@ For complex tasks, work through multiple steps:
2035
2035
  - Documents: ${homeDir}/Documents
2036
2036
  - Desktop: ${homeDir}/Desktop
2037
2037
  - Downloads: ${homeDir}/Downloads`;
2038
+ const serverTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
2039
+ const effectiveTimezone = userProfile?.timezone || serverTimezone;
2040
+ const now = /* @__PURE__ */ new Date();
2041
+ const timeStr = now.toLocaleTimeString("en-GB", {
2042
+ timeZone: effectiveTimezone,
2043
+ hour: "2-digit",
2044
+ minute: "2-digit"
2045
+ });
2046
+ const dateStr = now.toLocaleDateString("en-CA", { timeZone: effectiveTimezone });
2047
+ const dayStr = now.toLocaleDateString("en-US", { timeZone: effectiveTimezone, weekday: "long" });
2048
+ prompt += `
2049
+
2050
+ ## Current date & time`;
2051
+ prompt += `
2052
+ - Timezone: ${effectiveTimezone}`;
2053
+ prompt += `
2054
+ - Date: ${dateStr} (${dayStr})`;
2055
+ prompt += `
2056
+ - Time: ${timeStr}`;
2057
+ if (userProfile?.timezone && userProfile.timezone !== serverTimezone) {
2058
+ prompt += `
2059
+ - Server timezone: ${serverTimezone}`;
2060
+ }
2038
2061
  if (skills && skills.length > 0) {
2039
2062
  prompt += "\n\n## Available tools\n";
2040
2063
  for (const s of skills) {
@@ -2049,13 +2072,8 @@ For complex tasks, work through multiple steps:
2049
2072
  - Name: ${userProfile.displayName}`;
2050
2073
  }
2051
2074
  if (userProfile.timezone) {
2052
- const now = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB", {
2053
- timeZone: userProfile.timezone,
2054
- hour: "2-digit",
2055
- minute: "2-digit"
2056
- });
2057
2075
  prompt += `
2058
- - Timezone: ${userProfile.timezone} (Current local time: ${now})`;
2076
+ - Timezone: ${userProfile.timezone}`;
2059
2077
  }
2060
2078
  if (userProfile.language) {
2061
2079
  prompt += `
@@ -3158,9 +3176,9 @@ var init_reminder = __esm({
3158
3176
  reminderRepo;
3159
3177
  metadata = {
3160
3178
  name: "reminder",
3161
- description: 'Set timed reminders that notify the user later. Use when the user says "remind me", "erinnere mich", or asks to be notified about something at a specific time.',
3179
+ description: 'Set timed reminders that notify the user later. Use when the user says "remind me", "erinnere mich", or asks to be notified about something at a specific time. Prefer triggerAt (absolute time like "14:30" or "2026-02-28 09:00") over delayMinutes \u2014 it is more precise and avoids calculation errors.',
3162
3180
  riskLevel: "write",
3163
- version: "2.0.0",
3181
+ version: "3.0.0",
3164
3182
  inputSchema: {
3165
3183
  type: "object",
3166
3184
  properties: {
@@ -3173,9 +3191,13 @@ var init_reminder = __esm({
3173
3191
  type: "string",
3174
3192
  description: "The reminder message (required for set)"
3175
3193
  },
3194
+ triggerAt: {
3195
+ type: "string",
3196
+ description: 'Absolute time for the reminder. Accepts "HH:MM" for today or "YYYY-MM-DD HH:MM" for a specific date. Preferred over delayMinutes for time-specific reminders.'
3197
+ },
3176
3198
  delayMinutes: {
3177
3199
  type: "number",
3178
- description: "Minutes until the reminder triggers (required for set)"
3200
+ description: "Minutes until the reminder triggers. Use triggerAt instead when the user specifies a clock time."
3179
3201
  },
3180
3202
  reminderId: {
3181
3203
  type: "string",
@@ -3207,6 +3229,7 @@ var init_reminder = __esm({
3207
3229
  }
3208
3230
  setReminder(input2, context) {
3209
3231
  const message = input2.message;
3232
+ const triggerAtStr = input2.triggerAt;
3210
3233
  const delayMinutes = input2.delayMinutes;
3211
3234
  if (!message || typeof message !== "string") {
3212
3235
  return {
@@ -3214,20 +3237,119 @@ var init_reminder = __esm({
3214
3237
  error: 'Missing required field "message" for set action'
3215
3238
  };
3216
3239
  }
3217
- if (delayMinutes === void 0 || typeof delayMinutes !== "number" || delayMinutes <= 0) {
3240
+ let triggerAt;
3241
+ if (triggerAtStr && typeof triggerAtStr === "string") {
3242
+ const parsed = this.parseTriggerAt(triggerAtStr, context.timezone);
3243
+ if (!parsed) {
3244
+ return {
3245
+ success: false,
3246
+ error: `Could not parse triggerAt "${triggerAtStr}". Use "HH:MM" for today or "YYYY-MM-DD HH:MM" for a specific date.`
3247
+ };
3248
+ }
3249
+ if (parsed.getTime() <= Date.now()) {
3250
+ return {
3251
+ success: false,
3252
+ error: `The time "${triggerAtStr}" is in the past. Please specify a future time.`
3253
+ };
3254
+ }
3255
+ triggerAt = parsed;
3256
+ } else if (delayMinutes !== void 0 && typeof delayMinutes === "number" && delayMinutes > 0) {
3257
+ triggerAt = new Date(Date.now() + delayMinutes * 60 * 1e3);
3258
+ } else {
3218
3259
  return {
3219
3260
  success: false,
3220
- error: 'Missing or invalid "delayMinutes" for set action (must be a positive number)'
3261
+ error: 'Provide either "triggerAt" (e.g. "14:30") or "delayMinutes" (positive number) for set action.'
3221
3262
  };
3222
3263
  }
3223
- const triggerAt = new Date(Date.now() + delayMinutes * 60 * 1e3);
3224
3264
  const entry = this.reminderRepo.create(context.userId, context.platform, context.chatId, message, triggerAt);
3265
+ const delayMs = triggerAt.getTime() - Date.now();
3266
+ const mins = Math.round(delayMs / 6e4);
3267
+ const timeLabel = triggerAt.toLocaleTimeString("en-GB", {
3268
+ hour: "2-digit",
3269
+ minute: "2-digit",
3270
+ ...context.timezone ? { timeZone: context.timezone } : {}
3271
+ });
3225
3272
  return {
3226
3273
  success: true,
3227
3274
  data: { reminderId: entry.id, message, triggerAt: entry.triggerAt },
3228
- display: `Reminder set (${entry.id}): "${message}" in ${delayMinutes} minute(s)`
3275
+ display: `Reminder set (${entry.id}): "${message}" at ${timeLabel} (in ${mins} min)`
3229
3276
  };
3230
3277
  }
3278
+ /**
3279
+ * Parse a trigger time string into a Date.
3280
+ *
3281
+ * Supported formats:
3282
+ * - "HH:MM" → today at that time in the given timezone
3283
+ * - "YYYY-MM-DD HH:MM" → specific date+time
3284
+ */
3285
+ parseTriggerAt(str, timezone) {
3286
+ const trimmed = str.trim();
3287
+ const timeOnly = /^(\d{1,2}):(\d{2})$/.exec(trimmed);
3288
+ if (timeOnly) {
3289
+ const hours = parseInt(timeOnly[1], 10);
3290
+ const minutes = parseInt(timeOnly[2], 10);
3291
+ if (hours > 23 || minutes > 59)
3292
+ return void 0;
3293
+ return this.buildDateInTimezone(hours, minutes, void 0, timezone);
3294
+ }
3295
+ const dateTime = /^(\d{4})-(\d{2})-(\d{2})\s+(\d{1,2}):(\d{2})$/.exec(trimmed);
3296
+ if (dateTime) {
3297
+ const year = parseInt(dateTime[1], 10);
3298
+ const month = parseInt(dateTime[2], 10) - 1;
3299
+ const day = parseInt(dateTime[3], 10);
3300
+ const hours = parseInt(dateTime[4], 10);
3301
+ const minutes = parseInt(dateTime[5], 10);
3302
+ if (hours > 23 || minutes > 59 || month > 11 || day > 31)
3303
+ return void 0;
3304
+ return this.buildDateInTimezone(hours, minutes, { year, month, day }, timezone);
3305
+ }
3306
+ return void 0;
3307
+ }
3308
+ /**
3309
+ * Build a Date object for a given time in the user's timezone.
3310
+ * Uses iterative offset correction to handle DST edge cases.
3311
+ */
3312
+ buildDateInTimezone(hours, minutes, date, timezone) {
3313
+ if (!timezone) {
3314
+ const d = date ? new Date(date.year, date.month, date.day, hours, minutes, 0, 0) : /* @__PURE__ */ new Date();
3315
+ if (!date) {
3316
+ d.setHours(hours, minutes, 0, 0);
3317
+ }
3318
+ return d;
3319
+ }
3320
+ const now = /* @__PURE__ */ new Date();
3321
+ const refDate = date ? new Date(Date.UTC(date.year, date.month, date.day, hours, minutes, 0)) : new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes, 0));
3322
+ const formatter = new Intl.DateTimeFormat("en-CA", {
3323
+ timeZone: timezone,
3324
+ year: "numeric",
3325
+ month: "2-digit",
3326
+ day: "2-digit",
3327
+ hour: "2-digit",
3328
+ minute: "2-digit",
3329
+ second: "2-digit",
3330
+ hour12: false
3331
+ });
3332
+ if (!date) {
3333
+ const parts = formatter.formatToParts(now);
3334
+ const tzYear = parseInt(parts.find((p) => p.type === "year").value, 10);
3335
+ const tzMonth = parseInt(parts.find((p) => p.type === "month").value, 10) - 1;
3336
+ const tzDay = parseInt(parts.find((p) => p.type === "day").value, 10);
3337
+ let guess2 = new Date(Date.UTC(tzYear, tzMonth, tzDay, hours, minutes, 0));
3338
+ const guessParts2 = formatter.formatToParts(guess2);
3339
+ const guessHour2 = parseInt(guessParts2.find((p) => p.type === "hour").value, 10);
3340
+ const guessMinute2 = parseInt(guessParts2.find((p) => p.type === "minute").value, 10);
3341
+ const diffMinutes2 = (hours - guessHour2) * 60 + (minutes - guessMinute2);
3342
+ guess2 = new Date(guess2.getTime() + diffMinutes2 * 6e4);
3343
+ return guess2;
3344
+ }
3345
+ let guess = refDate;
3346
+ const guessParts = formatter.formatToParts(guess);
3347
+ const guessHour = parseInt(guessParts.find((p) => p.type === "hour").value, 10);
3348
+ const guessMinute = parseInt(guessParts.find((p) => p.type === "minute").value, 10);
3349
+ const diffMinutes = (hours - guessHour) * 60 + (minutes - guessMinute);
3350
+ guess = new Date(guess.getTime() + diffMinutes * 6e4);
3351
+ return guess;
3352
+ }
3231
3353
  listReminders(context) {
3232
3354
  const reminders = this.reminderRepo.getByUser(context.userId);
3233
3355
  const reminderList = reminders.map((r) => ({
@@ -6022,6 +6144,7 @@ var init_message_pipeline = __esm({
6022
6144
  }
6023
6145
  } catch {
6024
6146
  }
6147
+ const resolvedTimezone = userProfile?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
6025
6148
  const skillMetas = this.skillRegistry ? this.skillRegistry.getAll().map((s) => s.metadata) : void 0;
6026
6149
  const tools = skillMetas ? this.promptBuilder.buildTools(skillMetas) : void 0;
6027
6150
  let system = this.promptBuilder.buildSystemPrompt({
@@ -6076,7 +6199,8 @@ var init_message_pipeline = __esm({
6076
6199
  chatId: message.chatId,
6077
6200
  chatType: message.chatType,
6078
6201
  platform: message.platform,
6079
- conversationId: conversation.id
6202
+ conversationId: conversation.id,
6203
+ timezone: resolvedTimezone
6080
6204
  }, onProgress);
6081
6205
  toolResultBlocks.push({
6082
6206
  type: "tool_result",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madh-io/alfred-ai",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Alfred — Personal AI Assistant across Telegram, Discord, WhatsApp, Matrix & Signal",
5
5
  "type": "module",
6
6
  "bin": {