@madh-io/alfred-ai 0.5.0 → 0.7.0

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 +2471 -229
  2. package/package.json +4 -2
package/bundle/index.js CHANGED
@@ -11,7 +11,7 @@ var __export = (target, all) => {
11
11
 
12
12
  // ../config/dist/schema.js
13
13
  import { z } from "zod";
14
- var TelegramConfigSchema, DiscordConfigSchema, WhatsAppConfigSchema, MatrixConfigSchema, SignalConfigSchema, StorageConfigSchema, LoggerConfigSchema, SecurityConfigSchema, LLMProviderConfigSchema, SearchConfigSchema, EmailConfigSchema, AlfredConfigSchema;
14
+ var TelegramConfigSchema, DiscordConfigSchema, WhatsAppConfigSchema, MatrixConfigSchema, SignalConfigSchema, StorageConfigSchema, LoggerConfigSchema, SecurityConfigSchema, LLMProviderConfigSchema, SearchConfigSchema, EmailConfigSchema, SpeechConfigSchema, CalDAVConfigSchema, GoogleCalendarConfigSchema, MicrosoftCalendarConfigSchema, CalendarConfigSchema, AlfredConfigSchema;
15
15
  var init_schema = __esm({
16
16
  "../config/dist/schema.js"() {
17
17
  "use strict";
@@ -80,6 +80,33 @@ var init_schema = __esm({
80
80
  pass: z.string()
81
81
  })
82
82
  });
83
+ SpeechConfigSchema = z.object({
84
+ provider: z.enum(["openai", "groq"]),
85
+ apiKey: z.string(),
86
+ baseUrl: z.string().optional()
87
+ });
88
+ CalDAVConfigSchema = z.object({
89
+ serverUrl: z.string(),
90
+ username: z.string(),
91
+ password: z.string()
92
+ });
93
+ GoogleCalendarConfigSchema = z.object({
94
+ clientId: z.string(),
95
+ clientSecret: z.string(),
96
+ refreshToken: z.string()
97
+ });
98
+ MicrosoftCalendarConfigSchema = z.object({
99
+ clientId: z.string(),
100
+ clientSecret: z.string(),
101
+ tenantId: z.string(),
102
+ refreshToken: z.string()
103
+ });
104
+ CalendarConfigSchema = z.object({
105
+ provider: z.enum(["caldav", "google", "microsoft"]),
106
+ caldav: CalDAVConfigSchema.optional(),
107
+ google: GoogleCalendarConfigSchema.optional(),
108
+ microsoft: MicrosoftCalendarConfigSchema.optional()
109
+ });
83
110
  AlfredConfigSchema = z.object({
84
111
  name: z.string(),
85
112
  telegram: TelegramConfigSchema,
@@ -92,7 +119,9 @@ var init_schema = __esm({
92
119
  logger: LoggerConfigSchema,
93
120
  security: SecurityConfigSchema,
94
121
  search: SearchConfigSchema.optional(),
95
- email: EmailConfigSchema.optional()
122
+ email: EmailConfigSchema.optional(),
123
+ speech: SpeechConfigSchema.optional(),
124
+ calendar: CalendarConfigSchema.optional()
96
125
  });
97
126
  }
98
127
  });
@@ -212,7 +241,21 @@ var init_loader = __esm({
212
241
  ALFRED_SEARCH_API_KEY: ["search", "apiKey"],
213
242
  ALFRED_SEARCH_BASE_URL: ["search", "baseUrl"],
214
243
  ALFRED_EMAIL_USER: ["email", "auth", "user"],
215
- ALFRED_EMAIL_PASS: ["email", "auth", "pass"]
244
+ ALFRED_EMAIL_PASS: ["email", "auth", "pass"],
245
+ ALFRED_SPEECH_PROVIDER: ["speech", "provider"],
246
+ ALFRED_SPEECH_API_KEY: ["speech", "apiKey"],
247
+ ALFRED_SPEECH_BASE_URL: ["speech", "baseUrl"],
248
+ ALFRED_CALENDAR_PROVIDER: ["calendar", "provider"],
249
+ ALFRED_CALDAV_SERVER_URL: ["calendar", "caldav", "serverUrl"],
250
+ ALFRED_CALDAV_USERNAME: ["calendar", "caldav", "username"],
251
+ ALFRED_CALDAV_PASSWORD: ["calendar", "caldav", "password"],
252
+ ALFRED_GOOGLE_CALENDAR_CLIENT_ID: ["calendar", "google", "clientId"],
253
+ ALFRED_GOOGLE_CALENDAR_CLIENT_SECRET: ["calendar", "google", "clientSecret"],
254
+ ALFRED_GOOGLE_CALENDAR_REFRESH_TOKEN: ["calendar", "google", "refreshToken"],
255
+ ALFRED_MICROSOFT_CALENDAR_CLIENT_ID: ["calendar", "microsoft", "clientId"],
256
+ ALFRED_MICROSOFT_CALENDAR_CLIENT_SECRET: ["calendar", "microsoft", "clientSecret"],
257
+ ALFRED_MICROSOFT_CALENDAR_TENANT_ID: ["calendar", "microsoft", "tenantId"],
258
+ ALFRED_MICROSOFT_CALENDAR_REFRESH_TOKEN: ["calendar", "microsoft", "refreshToken"]
216
259
  };
217
260
  ConfigLoader = class {
218
261
  loadConfig(configPath) {
@@ -422,6 +465,43 @@ var init_migrations = __esm({
422
465
  ON notes(user_id, updated_at DESC);
423
466
  `);
424
467
  }
468
+ },
469
+ {
470
+ version: 5,
471
+ description: "Add user profile fields (timezone, language, bio, preferences)",
472
+ up(db) {
473
+ db.exec(`
474
+ ALTER TABLE users ADD COLUMN timezone TEXT;
475
+ ALTER TABLE users ADD COLUMN language TEXT;
476
+ ALTER TABLE users ADD COLUMN bio TEXT;
477
+ ALTER TABLE users ADD COLUMN preferences TEXT;
478
+ `);
479
+ }
480
+ },
481
+ {
482
+ version: 6,
483
+ description: "Add embeddings table for semantic search",
484
+ up(db) {
485
+ db.exec(`
486
+ CREATE TABLE IF NOT EXISTS embeddings (
487
+ id TEXT PRIMARY KEY,
488
+ user_id TEXT NOT NULL,
489
+ source_type TEXT NOT NULL,
490
+ source_id TEXT NOT NULL,
491
+ content TEXT NOT NULL,
492
+ embedding BLOB NOT NULL,
493
+ model TEXT NOT NULL,
494
+ dimensions INTEGER NOT NULL,
495
+ created_at TEXT NOT NULL
496
+ );
497
+
498
+ CREATE INDEX IF NOT EXISTS idx_embeddings_user
499
+ ON embeddings(user_id);
500
+
501
+ CREATE INDEX IF NOT EXISTS idx_embeddings_source
502
+ ON embeddings(source_type, source_id);
503
+ `);
504
+ }
425
505
  }
426
506
  ];
427
507
  }
@@ -652,6 +732,44 @@ var init_user_repository = __esm({
652
732
  values.push(id);
653
733
  this.db.prepare(`UPDATE users SET ${fields.join(", ")} WHERE id = ?`).run(...values);
654
734
  }
735
+ updateProfile(id, data) {
736
+ const fields = [];
737
+ const values = [];
738
+ if (data.timezone !== void 0) {
739
+ fields.push("timezone = ?");
740
+ values.push(data.timezone ?? null);
741
+ }
742
+ if (data.language !== void 0) {
743
+ fields.push("language = ?");
744
+ values.push(data.language ?? null);
745
+ }
746
+ if (data.bio !== void 0) {
747
+ fields.push("bio = ?");
748
+ values.push(data.bio ?? null);
749
+ }
750
+ if (data.preferences !== void 0) {
751
+ fields.push("preferences = ?");
752
+ values.push(data.preferences ? JSON.stringify(data.preferences) : null);
753
+ }
754
+ if (fields.length === 0)
755
+ return;
756
+ fields.push("updated_at = ?");
757
+ values.push((/* @__PURE__ */ new Date()).toISOString());
758
+ values.push(id);
759
+ this.db.prepare(`UPDATE users SET ${fields.join(", ")} WHERE id = ?`).run(...values);
760
+ }
761
+ getProfile(id) {
762
+ const row = this.db.prepare("SELECT display_name, timezone, language, bio, preferences FROM users WHERE id = ?").get(id);
763
+ if (!row)
764
+ return void 0;
765
+ return {
766
+ displayName: row.display_name ?? void 0,
767
+ timezone: row.timezone ?? void 0,
768
+ language: row.language ?? void 0,
769
+ bio: row.bio ?? void 0,
770
+ preferences: row.preferences ? JSON.parse(row.preferences) : void 0
771
+ };
772
+ }
655
773
  mapRow(row) {
656
774
  return {
657
775
  id: row.id,
@@ -659,6 +777,10 @@ var init_user_repository = __esm({
659
777
  platformUserId: row.platform_user_id,
660
778
  username: row.username ?? void 0,
661
779
  displayName: row.display_name ?? void 0,
780
+ timezone: row.timezone ?? void 0,
781
+ language: row.language ?? void 0,
782
+ bio: row.bio ?? void 0,
783
+ preferences: row.preferences ? JSON.parse(row.preferences) : void 0,
662
784
  createdAt: row.created_at,
663
785
  updatedAt: row.updated_at
664
786
  };
@@ -927,6 +1049,69 @@ var init_note_repository = __esm({
927
1049
  }
928
1050
  });
929
1051
 
1052
+ // ../storage/dist/repositories/embedding-repository.js
1053
+ import { randomUUID as randomUUID4 } from "node:crypto";
1054
+ var EmbeddingRepository;
1055
+ var init_embedding_repository = __esm({
1056
+ "../storage/dist/repositories/embedding-repository.js"() {
1057
+ "use strict";
1058
+ EmbeddingRepository = class {
1059
+ db;
1060
+ constructor(db) {
1061
+ this.db = db;
1062
+ }
1063
+ store(input2) {
1064
+ const id = randomUUID4();
1065
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1066
+ this.db.prepare("DELETE FROM embeddings WHERE user_id = ? AND source_type = ? AND source_id = ?").run(input2.userId, input2.sourceType, input2.sourceId);
1067
+ const buffer = Buffer.from(new Float32Array(input2.embedding).buffer);
1068
+ this.db.prepare("INSERT INTO embeddings (id, user_id, source_type, source_id, content, embedding, model, dimensions, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(id, input2.userId, input2.sourceType, input2.sourceId, input2.content, buffer, input2.model, input2.dimensions, now);
1069
+ return {
1070
+ id,
1071
+ userId: input2.userId,
1072
+ sourceType: input2.sourceType,
1073
+ sourceId: input2.sourceId,
1074
+ content: input2.content,
1075
+ embedding: input2.embedding,
1076
+ model: input2.model,
1077
+ dimensions: input2.dimensions,
1078
+ createdAt: now
1079
+ };
1080
+ }
1081
+ findByUser(userId) {
1082
+ const rows = this.db.prepare("SELECT * FROM embeddings WHERE user_id = ? ORDER BY created_at DESC").all(userId);
1083
+ return rows.map((row) => this.mapRow(row));
1084
+ }
1085
+ findBySource(sourceType, sourceId) {
1086
+ const row = this.db.prepare("SELECT * FROM embeddings WHERE source_type = ? AND source_id = ?").get(sourceType, sourceId);
1087
+ if (!row)
1088
+ return void 0;
1089
+ return this.mapRow(row);
1090
+ }
1091
+ delete(sourceType, sourceId) {
1092
+ const result = this.db.prepare("DELETE FROM embeddings WHERE source_type = ? AND source_id = ?").run(sourceType, sourceId);
1093
+ return result.changes > 0;
1094
+ }
1095
+ mapRow(row) {
1096
+ const blob = row.embedding;
1097
+ const float32 = new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / 4);
1098
+ const embedding = Array.from(float32);
1099
+ return {
1100
+ id: row.id,
1101
+ userId: row.user_id,
1102
+ sourceType: row.source_type,
1103
+ sourceId: row.source_id,
1104
+ content: row.content,
1105
+ embedding,
1106
+ model: row.model,
1107
+ dimensions: row.dimensions,
1108
+ createdAt: row.created_at
1109
+ };
1110
+ }
1111
+ };
1112
+ }
1113
+ });
1114
+
930
1115
  // ../storage/dist/index.js
931
1116
  var init_dist3 = __esm({
932
1117
  "../storage/dist/index.js"() {
@@ -940,6 +1125,7 @@ var init_dist3 = __esm({
940
1125
  init_migrations();
941
1126
  init_reminder_repository();
942
1127
  init_note_repository();
1128
+ init_embedding_repository();
943
1129
  }
944
1130
  });
945
1131
 
@@ -994,6 +1180,12 @@ var init_provider = __esm({
994
1180
  getContextWindow() {
995
1181
  return this.contextWindow;
996
1182
  }
1183
+ async embed(_text) {
1184
+ return void 0;
1185
+ }
1186
+ supportsEmbeddings() {
1187
+ return false;
1188
+ }
997
1189
  };
998
1190
  }
999
1191
  });
@@ -1082,6 +1274,15 @@ var init_anthropic = __esm({
1082
1274
  switch (block.type) {
1083
1275
  case "text":
1084
1276
  return { type: "text", text: block.text };
1277
+ case "image":
1278
+ return {
1279
+ type: "image",
1280
+ source: {
1281
+ type: "base64",
1282
+ media_type: block.source.media_type,
1283
+ data: block.source.data
1284
+ }
1285
+ };
1085
1286
  case "tool_use":
1086
1287
  return {
1087
1288
  type: "tool_use",
@@ -1260,6 +1461,25 @@ var init_openai = __esm({
1260
1461
  isAvailable() {
1261
1462
  return !!this.config.apiKey;
1262
1463
  }
1464
+ async embed(text) {
1465
+ try {
1466
+ const response = await this.client.embeddings.create({
1467
+ model: "text-embedding-3-small",
1468
+ input: text
1469
+ });
1470
+ const data = response.data[0];
1471
+ return {
1472
+ embedding: data.embedding,
1473
+ model: "text-embedding-3-small",
1474
+ dimensions: data.embedding.length
1475
+ };
1476
+ } catch {
1477
+ return void 0;
1478
+ }
1479
+ }
1480
+ supportsEmbeddings() {
1481
+ return true;
1482
+ }
1263
1483
  mapMessages(messages, system) {
1264
1484
  const mapped = [];
1265
1485
  if (system) {
@@ -1278,6 +1498,14 @@ var init_openai = __esm({
1278
1498
  case "text":
1279
1499
  textParts.push({ type: "text", text: block.text });
1280
1500
  break;
1501
+ case "image":
1502
+ textParts.push({
1503
+ type: "image_url",
1504
+ image_url: {
1505
+ url: `data:${block.source.media_type};base64,${block.source.data}`
1506
+ }
1507
+ });
1508
+ break;
1281
1509
  case "tool_use":
1282
1510
  toolUseParts.push({
1283
1511
  id: block.id,
@@ -1382,6 +1610,9 @@ var init_openrouter = __esm({
1382
1610
  isAvailable() {
1383
1611
  return !!this.config.apiKey;
1384
1612
  }
1613
+ supportsEmbeddings() {
1614
+ return false;
1615
+ }
1385
1616
  };
1386
1617
  }
1387
1618
  });
@@ -1608,6 +1839,34 @@ var init_ollama = __esm({
1608
1839
  return false;
1609
1840
  }
1610
1841
  }
1842
+ async embed(text) {
1843
+ try {
1844
+ const res = await fetch(`${this.baseUrl}/api/embed`, {
1845
+ method: "POST",
1846
+ headers: this.getHeaders(),
1847
+ body: JSON.stringify({
1848
+ model: "nomic-embed-text",
1849
+ input: text
1850
+ })
1851
+ });
1852
+ if (!res.ok)
1853
+ return void 0;
1854
+ const data = await res.json();
1855
+ if (!data.embeddings || data.embeddings.length === 0)
1856
+ return void 0;
1857
+ const embedding = data.embeddings[0];
1858
+ return {
1859
+ embedding,
1860
+ model: "nomic-embed-text",
1861
+ dimensions: embedding.length
1862
+ };
1863
+ } catch {
1864
+ return void 0;
1865
+ }
1866
+ }
1867
+ supportsEmbeddings() {
1868
+ return true;
1869
+ }
1611
1870
  buildOptions(request) {
1612
1871
  const options = {};
1613
1872
  const temperature = request.temperature ?? this.config.temperature;
@@ -1636,11 +1895,15 @@ var init_ollama = __esm({
1636
1895
  }
1637
1896
  mapContentBlocks(role, blocks) {
1638
1897
  const textParts = [];
1898
+ const images = [];
1639
1899
  for (const block of blocks) {
1640
1900
  switch (block.type) {
1641
1901
  case "text":
1642
1902
  textParts.push(block.text);
1643
1903
  break;
1904
+ case "image":
1905
+ images.push(block.source.data);
1906
+ break;
1644
1907
  case "tool_use":
1645
1908
  textParts.push(`[Tool call: ${block.name}(${JSON.stringify(block.input)})]`);
1646
1909
  break;
@@ -1649,7 +1912,11 @@ var init_ollama = __esm({
1649
1912
  break;
1650
1913
  }
1651
1914
  }
1652
- return { role, content: textParts.join("\n") };
1915
+ const msg = { role, content: textParts.join("\n") };
1916
+ if (images.length > 0) {
1917
+ msg.images = images;
1918
+ }
1919
+ return msg;
1653
1920
  }
1654
1921
  mapTools(tools) {
1655
1922
  return tools.map((tool) => ({
@@ -1725,6 +1992,9 @@ function estimateMessageTokens(msg) {
1725
1992
  case "text":
1726
1993
  tokens += estimateTokens(block.text);
1727
1994
  break;
1995
+ case "image":
1996
+ tokens += 1e3;
1997
+ break;
1728
1998
  case "tool_use":
1729
1999
  tokens += estimateTokens(block.name) + estimateTokens(JSON.stringify(block.input));
1730
2000
  break;
@@ -1740,7 +2010,8 @@ var init_prompt_builder = __esm({
1740
2010
  "../llm/dist/prompt-builder.js"() {
1741
2011
  "use strict";
1742
2012
  PromptBuilder = class {
1743
- buildSystemPrompt(memories, skills) {
2013
+ buildSystemPrompt(context = {}) {
2014
+ const { memories, skills, userProfile, todayEvents } = context;
1744
2015
  const os3 = process.platform === "darwin" ? "macOS" : process.platform === "win32" ? "Windows" : "Linux";
1745
2016
  const homeDir = process.env["HOME"] || process.env["USERPROFILE"] || "~";
1746
2017
  let prompt = `You are Alfred, a personal AI assistant. You run on ${os3} (home: ${homeDir}).
@@ -1771,6 +2042,48 @@ For complex tasks, work through multiple steps:
1771
2042
  `;
1772
2043
  }
1773
2044
  }
2045
+ if (userProfile) {
2046
+ prompt += "\n\n## User profile";
2047
+ if (userProfile.displayName) {
2048
+ prompt += `
2049
+ - Name: ${userProfile.displayName}`;
2050
+ }
2051
+ 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
+ prompt += `
2058
+ - Timezone: ${userProfile.timezone} (Current local time: ${now})`;
2059
+ }
2060
+ if (userProfile.language) {
2061
+ prompt += `
2062
+ - Language: ${userProfile.language}`;
2063
+ }
2064
+ if (userProfile.bio) {
2065
+ prompt += `
2066
+ - Bio: ${userProfile.bio}`;
2067
+ }
2068
+ }
2069
+ if (todayEvents && todayEvents.length > 0) {
2070
+ prompt += "\n\n## Today's events";
2071
+ for (const event of todayEvents) {
2072
+ const startTime = event.allDay ? "All day" : event.start.toLocaleTimeString("en-GB", {
2073
+ hour: "2-digit",
2074
+ minute: "2-digit",
2075
+ ...userProfile?.timezone ? { timeZone: userProfile.timezone } : {}
2076
+ });
2077
+ const endTime = event.allDay ? "" : `-${event.end.toLocaleTimeString("en-GB", {
2078
+ hour: "2-digit",
2079
+ minute: "2-digit",
2080
+ ...userProfile?.timezone ? { timeZone: userProfile.timezone } : {}
2081
+ })}`;
2082
+ const location = event.location ? ` @ ${event.location}` : "";
2083
+ prompt += `
2084
+ - ${startTime}${endTime}: ${event.title}${location}`;
2085
+ }
2086
+ }
1774
2087
  if (memories && memories.length > 0) {
1775
2088
  prompt += "\n\n## Memories about this user\n";
1776
2089
  for (const m of memories) {
@@ -2223,7 +2536,8 @@ var init_skill_sandbox = __esm({
2223
2536
  constructor(logger) {
2224
2537
  this.logger = logger;
2225
2538
  }
2226
- async execute(skill, input2, context, timeoutMs = DEFAULT_TIMEOUT_MS) {
2539
+ async execute(skill, input2, context, timeoutMs) {
2540
+ timeoutMs = timeoutMs ?? skill.metadata.timeoutMs ?? DEFAULT_TIMEOUT_MS;
2227
2541
  const { name } = skill.metadata;
2228
2542
  this.logger.info({ skill: name, input: input2 }, "Skill execution started");
2229
2543
  try {
@@ -3086,6 +3400,7 @@ var init_memory = __esm({
3086
3400
  init_skill();
3087
3401
  MemorySkill = class extends Skill {
3088
3402
  memoryRepo;
3403
+ embeddingService;
3089
3404
  metadata = {
3090
3405
  name: "memory",
3091
3406
  description: "Store and retrieve persistent memories. Use this to remember user preferences, facts, and important information across conversations.",
@@ -3096,7 +3411,7 @@ var init_memory = __esm({
3096
3411
  properties: {
3097
3412
  action: {
3098
3413
  type: "string",
3099
- enum: ["save", "recall", "search", "list", "delete"],
3414
+ enum: ["save", "recall", "search", "list", "delete", "semantic_search"],
3100
3415
  description: "The memory action to perform"
3101
3416
  },
3102
3417
  key: {
@@ -3119,9 +3434,10 @@ var init_memory = __esm({
3119
3434
  required: ["action"]
3120
3435
  }
3121
3436
  };
3122
- constructor(memoryRepo) {
3437
+ constructor(memoryRepo, embeddingService) {
3123
3438
  super();
3124
3439
  this.memoryRepo = memoryRepo;
3440
+ this.embeddingService = embeddingService;
3125
3441
  }
3126
3442
  async execute(input2, context) {
3127
3443
  const action = input2.action;
@@ -3136,10 +3452,12 @@ var init_memory = __esm({
3136
3452
  return this.listMemories(input2, context);
3137
3453
  case "delete":
3138
3454
  return this.deleteMemory(input2, context);
3455
+ case "semantic_search":
3456
+ return this.semanticSearchMemories(input2, context);
3139
3457
  default:
3140
3458
  return {
3141
3459
  success: false,
3142
- error: `Unknown action: "${String(action)}". Valid actions: save, recall, search, list, delete`
3460
+ error: `Unknown action: "${String(action)}". Valid actions: save, recall, search, list, delete, semantic_search`
3143
3461
  };
3144
3462
  }
3145
3463
  }
@@ -3160,6 +3478,10 @@ var init_memory = __esm({
3160
3478
  };
3161
3479
  }
3162
3480
  const entry = this.memoryRepo.save(context.userId, key, value, category ?? "general");
3481
+ if (this.embeddingService) {
3482
+ this.embeddingService.embedAndStore(context.userId, `${key}: ${value}`, "memory", key).catch(() => {
3483
+ });
3484
+ }
3163
3485
  return {
3164
3486
  success: true,
3165
3487
  data: entry,
@@ -3230,6 +3552,25 @@ ${entries.map((e) => `- [${e.category}] ${e.key}: "${e.value}"`).join("\n")}`
3230
3552
  display: deleted ? `Memory "${key}" deleted.` : `No memory found for key "${key}".`
3231
3553
  };
3232
3554
  }
3555
+ async semanticSearchMemories(input2, context) {
3556
+ const query = input2.query;
3557
+ if (!query || typeof query !== "string") {
3558
+ return { success: false, error: 'Missing required field "query" for semantic_search action' };
3559
+ }
3560
+ if (!this.embeddingService) {
3561
+ return this.searchMemories(input2, context);
3562
+ }
3563
+ const results = await this.embeddingService.semanticSearch(context.userId, query, 10);
3564
+ if (results.length === 0) {
3565
+ return this.searchMemories(input2, context);
3566
+ }
3567
+ return {
3568
+ success: true,
3569
+ data: results,
3570
+ display: `Found ${results.length} semantically related memory(ies):
3571
+ ${results.map((r) => `- ${r.key}: "${r.value}" (score: ${r.score.toFixed(2)})`).join("\n")}`
3572
+ };
3573
+ }
3233
3574
  };
3234
3575
  }
3235
3576
  });
@@ -3251,6 +3592,8 @@ var init_delegate = __esm({
3251
3592
  description: 'Delegate a complex sub-task to an autonomous sub-agent that has full tool access. The sub-agent can use shell, web search, calculator, memory, email, and all other tools. Use when a task is independent enough to run in parallel or when it requires a focused, multi-step workflow (e.g. "research X and summarize", "find all TODO files and list them", "check the weather and draft a packing list"). The sub-agent runs up to 5 tool iterations autonomously.',
3252
3593
  riskLevel: "write",
3253
3594
  version: "2.0.0",
3595
+ timeoutMs: 12e4,
3596
+ // 2 minutes — delegate chains multiple LLM calls + tool executions
3254
3597
  inputSchema: {
3255
3598
  type: "object",
3256
3599
  properties: {
@@ -3847,24 +4190,28 @@ var init_file = __esm({
3847
4190
  FileSkill = class extends Skill {
3848
4191
  metadata = {
3849
4192
  name: "file",
3850
- description: "Read or write files safely. Use for reading file contents, writing text to files, listing directory contents, or getting file info. Prefer this over shell for simple file operations.",
4193
+ description: 'Read, write, move, or copy files. Use for reading file contents, writing text to files, saving binary data, listing directory contents, moving/copying files, or getting file info. Prefer this over shell for file operations. When a user sends a file attachment, it is saved to the inbox \u2014 use "move" to relocate it.',
3851
4194
  riskLevel: "write",
3852
- version: "1.0.0",
4195
+ version: "2.0.0",
3853
4196
  inputSchema: {
3854
4197
  type: "object",
3855
4198
  properties: {
3856
4199
  action: {
3857
4200
  type: "string",
3858
- enum: ["read", "write", "append", "list", "info", "exists"],
4201
+ enum: ["read", "write", "write_binary", "append", "list", "info", "exists", "move", "copy", "delete"],
3859
4202
  description: "The file operation to perform"
3860
4203
  },
3861
4204
  path: {
3862
4205
  type: "string",
3863
4206
  description: "Absolute or relative file/directory path (~ expands to home)"
3864
4207
  },
4208
+ destination: {
4209
+ type: "string",
4210
+ description: "Destination path for move/copy actions (~ expands to home)"
4211
+ },
3865
4212
  content: {
3866
4213
  type: "string",
3867
- description: "Content to write (required for write/append)"
4214
+ description: "Content to write (required for write/append; base64-encoded for write_binary)"
3868
4215
  }
3869
4216
  },
3870
4217
  required: ["action", "path"]
@@ -3874,6 +4221,7 @@ var init_file = __esm({
3874
4221
  const action = input2.action;
3875
4222
  const rawPath = input2.path;
3876
4223
  const content = input2.content;
4224
+ const destination = input2.destination;
3877
4225
  if (!action || !rawPath) {
3878
4226
  return { success: false, error: 'Missing required fields "action" and "path"' };
3879
4227
  }
@@ -3883,6 +4231,8 @@ var init_file = __esm({
3883
4231
  return this.readFile(resolvedPath);
3884
4232
  case "write":
3885
4233
  return this.writeFile(resolvedPath, content);
4234
+ case "write_binary":
4235
+ return this.writeBinaryFile(resolvedPath, content);
3886
4236
  case "append":
3887
4237
  return this.appendFile(resolvedPath, content);
3888
4238
  case "list":
@@ -3891,8 +4241,14 @@ var init_file = __esm({
3891
4241
  return this.fileInfo(resolvedPath);
3892
4242
  case "exists":
3893
4243
  return this.fileExists(resolvedPath);
4244
+ case "move":
4245
+ return this.moveFile(resolvedPath, destination);
4246
+ case "copy":
4247
+ return this.copyFile(resolvedPath, destination);
4248
+ case "delete":
4249
+ return this.deleteFile(resolvedPath);
3894
4250
  default:
3895
- return { success: false, error: `Unknown action "${action}". Valid: read, write, append, list, info, exists` };
4251
+ return { success: false, error: `Unknown action "${action}". Valid: read, write, write_binary, append, list, info, exists, move, copy, delete` };
3896
4252
  }
3897
4253
  }
3898
4254
  resolvePath(raw) {
@@ -4001,6 +4357,89 @@ Modified: ${info.modified}`
4001
4357
  display: exists ? `Yes, "${filePath}" exists` : `No, "${filePath}" does not exist`
4002
4358
  };
4003
4359
  }
4360
+ writeBinaryFile(filePath, base64Content) {
4361
+ if (!base64Content) {
4362
+ return { success: false, error: 'Missing "content" (base64-encoded) for write_binary action' };
4363
+ }
4364
+ try {
4365
+ const dir = path4.dirname(filePath);
4366
+ fs4.mkdirSync(dir, { recursive: true });
4367
+ const buffer = Buffer.from(base64Content, "base64");
4368
+ fs4.writeFileSync(filePath, buffer);
4369
+ return {
4370
+ success: true,
4371
+ data: { path: filePath, bytes: buffer.length },
4372
+ display: `Written ${buffer.length} bytes (binary) to ${filePath}`
4373
+ };
4374
+ } catch (err) {
4375
+ return { success: false, error: `Cannot write "${filePath}": ${err.message}` };
4376
+ }
4377
+ }
4378
+ moveFile(source, destination) {
4379
+ if (!destination) {
4380
+ return { success: false, error: 'Missing "destination" for move action' };
4381
+ }
4382
+ const resolvedDest = this.resolvePath(destination);
4383
+ try {
4384
+ const destDir = path4.dirname(resolvedDest);
4385
+ fs4.mkdirSync(destDir, { recursive: true });
4386
+ fs4.renameSync(source, resolvedDest);
4387
+ return {
4388
+ success: true,
4389
+ data: { from: source, to: resolvedDest },
4390
+ display: `Moved ${source} \u2192 ${resolvedDest}`
4391
+ };
4392
+ } catch (err) {
4393
+ try {
4394
+ fs4.copyFileSync(source, resolvedDest);
4395
+ fs4.unlinkSync(source);
4396
+ return {
4397
+ success: true,
4398
+ data: { from: source, to: resolvedDest },
4399
+ display: `Moved ${source} \u2192 ${resolvedDest}`
4400
+ };
4401
+ } catch (err2) {
4402
+ return { success: false, error: `Cannot move "${source}" to "${resolvedDest}": ${err2.message}` };
4403
+ }
4404
+ }
4405
+ }
4406
+ copyFile(source, destination) {
4407
+ if (!destination) {
4408
+ return { success: false, error: 'Missing "destination" for copy action' };
4409
+ }
4410
+ const resolvedDest = this.resolvePath(destination);
4411
+ try {
4412
+ const destDir = path4.dirname(resolvedDest);
4413
+ fs4.mkdirSync(destDir, { recursive: true });
4414
+ fs4.copyFileSync(source, resolvedDest);
4415
+ return {
4416
+ success: true,
4417
+ data: { from: source, to: resolvedDest },
4418
+ display: `Copied ${source} \u2192 ${resolvedDest}`
4419
+ };
4420
+ } catch (err) {
4421
+ return { success: false, error: `Cannot copy "${source}" to "${resolvedDest}": ${err.message}` };
4422
+ }
4423
+ }
4424
+ deleteFile(filePath) {
4425
+ try {
4426
+ if (!fs4.existsSync(filePath)) {
4427
+ return { success: false, error: `"${filePath}" does not exist` };
4428
+ }
4429
+ const stat = fs4.statSync(filePath);
4430
+ if (stat.isDirectory()) {
4431
+ return { success: false, error: `"${filePath}" is a directory. Use shell for directory deletion.` };
4432
+ }
4433
+ fs4.unlinkSync(filePath);
4434
+ return {
4435
+ success: true,
4436
+ data: { path: filePath },
4437
+ display: `Deleted ${filePath}`
4438
+ };
4439
+ } catch (err) {
4440
+ return { success: false, error: `Cannot delete "${filePath}": ${err.message}` };
4441
+ }
4442
+ }
4004
4443
  };
4005
4444
  }
4006
4445
  });
@@ -4425,69 +4864,877 @@ ${cleaned}`
4425
4864
  }
4426
4865
  });
4427
4866
 
4428
- // ../skills/dist/index.js
4429
- var init_dist6 = __esm({
4430
- "../skills/dist/index.js"() {
4867
+ // ../skills/dist/built-in/profile.js
4868
+ var ProfileSkill;
4869
+ var init_profile = __esm({
4870
+ "../skills/dist/built-in/profile.js"() {
4431
4871
  "use strict";
4432
4872
  init_skill();
4433
- init_skill_registry();
4434
- init_skill_sandbox();
4435
- init_plugin_loader();
4436
- init_calculator();
4437
- init_system_info();
4438
- init_web_search();
4439
- init_reminder();
4440
- init_note();
4441
- init_weather();
4442
- init_shell();
4443
- init_memory();
4444
- init_delegate();
4445
- init_email();
4446
- init_http();
4447
- init_file();
4448
- init_clipboard();
4449
- init_screenshot();
4450
- init_browser();
4451
- }
4452
- });
4453
-
4454
- // ../core/dist/conversation-manager.js
4455
- var ConversationManager;
4456
- var init_conversation_manager = __esm({
4457
- "../core/dist/conversation-manager.js"() {
4458
- "use strict";
4459
- ConversationManager = class {
4460
- conversations;
4461
- constructor(conversations) {
4462
- this.conversations = conversations;
4873
+ ProfileSkill = class extends Skill {
4874
+ userRepo;
4875
+ metadata = {
4876
+ name: "profile",
4877
+ description: "Manage user profile settings including timezone, language, and bio. Use this to personalize Alfred for each user.",
4878
+ riskLevel: "write",
4879
+ version: "1.0.0",
4880
+ inputSchema: {
4881
+ type: "object",
4882
+ properties: {
4883
+ action: {
4884
+ type: "string",
4885
+ enum: ["get", "set_timezone", "set_language", "set_bio", "set_preference"],
4886
+ description: "The profile action to perform"
4887
+ },
4888
+ value: {
4889
+ type: "string",
4890
+ description: "The value to set (for set_* actions)"
4891
+ },
4892
+ preference_key: {
4893
+ type: "string",
4894
+ description: "The preference key (for set_preference)"
4895
+ },
4896
+ preference_value: {
4897
+ type: "string",
4898
+ description: "The preference value (for set_preference)"
4899
+ }
4900
+ },
4901
+ required: ["action"]
4902
+ }
4903
+ };
4904
+ constructor(userRepo) {
4905
+ super();
4906
+ this.userRepo = userRepo;
4463
4907
  }
4464
- getOrCreateConversation(platform, chatId, userId) {
4465
- const existing = this.conversations.findByPlatformChat(platform, chatId);
4466
- if (existing) {
4467
- this.conversations.updateTimestamp(existing.id);
4468
- return existing;
4908
+ async execute(input2, context) {
4909
+ const action = input2.action;
4910
+ const user = this.userRepo.findOrCreate(context.platform, context.userId);
4911
+ switch (action) {
4912
+ case "get":
4913
+ return this.getProfile(user.id);
4914
+ case "set_timezone":
4915
+ return this.setField(user.id, "timezone", input2.value);
4916
+ case "set_language":
4917
+ return this.setField(user.id, "language", input2.value);
4918
+ case "set_bio":
4919
+ return this.setField(user.id, "bio", input2.value);
4920
+ case "set_preference":
4921
+ return this.setPreference(user.id, input2.preference_key, input2.preference_value);
4922
+ default:
4923
+ return { success: false, error: `Unknown action: "${String(action)}"` };
4924
+ }
4925
+ }
4926
+ getProfile(userId) {
4927
+ const profile = this.userRepo.getProfile(userId);
4928
+ if (!profile) {
4929
+ return { success: true, data: null, display: "No profile found. Set your timezone, language, or bio to create one." };
4930
+ }
4931
+ const parts = [];
4932
+ if (profile.displayName)
4933
+ parts.push(`Name: ${profile.displayName}`);
4934
+ if (profile.timezone)
4935
+ parts.push(`Timezone: ${profile.timezone}`);
4936
+ if (profile.language)
4937
+ parts.push(`Language: ${profile.language}`);
4938
+ if (profile.bio)
4939
+ parts.push(`Bio: ${profile.bio}`);
4940
+ if (profile.preferences) {
4941
+ for (const [key, value] of Object.entries(profile.preferences)) {
4942
+ parts.push(`${key}: ${String(value)}`);
4943
+ }
4469
4944
  }
4470
- return this.conversations.create(platform, chatId, userId);
4945
+ return {
4946
+ success: true,
4947
+ data: profile,
4948
+ display: parts.length > 0 ? `Profile:
4949
+ ${parts.map((p) => `- ${p}`).join("\n")}` : "Profile is empty."
4950
+ };
4471
4951
  }
4472
- addMessage(conversationId, role, content, toolCalls) {
4473
- return this.conversations.addMessage(conversationId, role, content, toolCalls);
4952
+ setField(userId, field, value) {
4953
+ if (!value || typeof value !== "string") {
4954
+ return { success: false, error: `Missing required "value" for ${field}` };
4955
+ }
4956
+ this.userRepo.updateProfile(userId, { [field]: value });
4957
+ return { success: true, data: { [field]: value }, display: `${field} set to "${value}"` };
4474
4958
  }
4475
- getHistory(conversationId, limit = 20) {
4476
- return this.conversations.getMessages(conversationId, limit);
4959
+ setPreference(userId, key, value) {
4960
+ if (!key || typeof key !== "string") {
4961
+ return { success: false, error: 'Missing required "preference_key"' };
4962
+ }
4963
+ const profile = this.userRepo.getProfile(userId);
4964
+ const prefs = profile?.preferences ?? {};
4965
+ prefs[key] = value;
4966
+ this.userRepo.updateProfile(userId, { preferences: prefs });
4967
+ return { success: true, data: { key, value }, display: `Preference "${key}" set to "${value}"` };
4477
4968
  }
4478
4969
  };
4479
4970
  }
4480
4971
  });
4481
4972
 
4482
- // ../core/dist/message-pipeline.js
4483
- var MAX_TOOL_ITERATIONS, TOKEN_BUDGET_RATIO, MessagePipeline;
4484
- var init_message_pipeline = __esm({
4485
- "../core/dist/message-pipeline.js"() {
4973
+ // ../skills/dist/built-in/calendar/calendar-provider.js
4974
+ var CalendarProvider;
4975
+ var init_calendar_provider = __esm({
4976
+ "../skills/dist/built-in/calendar/calendar-provider.js"() {
4977
+ "use strict";
4978
+ CalendarProvider = class {
4979
+ };
4980
+ }
4981
+ });
4982
+
4983
+ // ../skills/dist/built-in/calendar/caldav-provider.js
4984
+ var caldav_provider_exports = {};
4985
+ __export(caldav_provider_exports, {
4986
+ CalDAVProvider: () => CalDAVProvider
4987
+ });
4988
+ var CalDAVProvider;
4989
+ var init_caldav_provider = __esm({
4990
+ "../skills/dist/built-in/calendar/caldav-provider.js"() {
4991
+ "use strict";
4992
+ init_calendar_provider();
4993
+ CalDAVProvider = class extends CalendarProvider {
4994
+ config;
4995
+ client;
4996
+ constructor(config) {
4997
+ super();
4998
+ this.config = config;
4999
+ }
5000
+ async initialize() {
5001
+ try {
5002
+ const tsdav = await import("tsdav");
5003
+ const { createDAVClient } = tsdav;
5004
+ this.client = await createDAVClient({
5005
+ serverUrl: this.config.serverUrl,
5006
+ credentials: {
5007
+ username: this.config.username,
5008
+ password: this.config.password
5009
+ },
5010
+ authMethod: "Basic",
5011
+ defaultAccountType: "caldav"
5012
+ });
5013
+ } catch (err) {
5014
+ throw new Error(`CalDAV initialization failed: ${err instanceof Error ? err.message : String(err)}`);
5015
+ }
5016
+ }
5017
+ async listEvents(start, end) {
5018
+ const calendars = await this.client.fetchCalendars();
5019
+ if (!calendars || calendars.length === 0)
5020
+ return [];
5021
+ const events = [];
5022
+ for (const calendar of calendars) {
5023
+ const objects = await this.client.fetchCalendarObjects({
5024
+ calendar,
5025
+ timeRange: {
5026
+ start: start.toISOString(),
5027
+ end: end.toISOString()
5028
+ }
5029
+ });
5030
+ for (const obj of objects) {
5031
+ const parsed = this.parseICalEvent(obj.data, obj.url);
5032
+ if (parsed)
5033
+ events.push(parsed);
5034
+ }
5035
+ }
5036
+ return events.sort((a, b) => a.start.getTime() - b.start.getTime());
5037
+ }
5038
+ async createEvent(input2) {
5039
+ const calendars = await this.client.fetchCalendars();
5040
+ if (!calendars || calendars.length === 0) {
5041
+ throw new Error("No calendars found");
5042
+ }
5043
+ const uid = `alfred-${Date.now()}@alfred`;
5044
+ const ical = this.buildICalEvent(uid, input2);
5045
+ await this.client.createCalendarObject({
5046
+ calendar: calendars[0],
5047
+ filename: `${uid}.ics`,
5048
+ iCalString: ical
5049
+ });
5050
+ return {
5051
+ id: uid,
5052
+ title: input2.title,
5053
+ start: input2.start,
5054
+ end: input2.end,
5055
+ location: input2.location,
5056
+ description: input2.description,
5057
+ allDay: input2.allDay
5058
+ };
5059
+ }
5060
+ async updateEvent(id, input2) {
5061
+ const calendars = await this.client.fetchCalendars();
5062
+ for (const calendar of calendars) {
5063
+ const objects = await this.client.fetchCalendarObjects({ calendar });
5064
+ for (const obj of objects) {
5065
+ if (obj.url?.includes(id) || obj.data?.includes(id)) {
5066
+ const existing = this.parseICalEvent(obj.data, obj.url);
5067
+ if (!existing)
5068
+ continue;
5069
+ const updated = {
5070
+ title: input2.title ?? existing.title,
5071
+ start: input2.start ?? existing.start,
5072
+ end: input2.end ?? existing.end,
5073
+ location: input2.location ?? existing.location,
5074
+ description: input2.description ?? existing.description,
5075
+ allDay: input2.allDay ?? existing.allDay
5076
+ };
5077
+ const ical = this.buildICalEvent(id, updated);
5078
+ await this.client.updateCalendarObject({
5079
+ calendarObject: { ...obj, data: ical }
5080
+ });
5081
+ return { id, ...updated };
5082
+ }
5083
+ }
5084
+ }
5085
+ throw new Error(`Event ${id} not found`);
5086
+ }
5087
+ async deleteEvent(id) {
5088
+ const calendars = await this.client.fetchCalendars();
5089
+ for (const calendar of calendars) {
5090
+ const objects = await this.client.fetchCalendarObjects({ calendar });
5091
+ for (const obj of objects) {
5092
+ if (obj.url?.includes(id) || obj.data?.includes(id)) {
5093
+ await this.client.deleteCalendarObject({ calendarObject: obj });
5094
+ return;
5095
+ }
5096
+ }
5097
+ }
5098
+ throw new Error(`Event ${id} not found`);
5099
+ }
5100
+ async checkAvailability(start, end) {
5101
+ const events = await this.listEvents(start, end);
5102
+ const conflicts = events.filter((e) => !e.allDay && e.start < end && e.end > start);
5103
+ return { available: conflicts.length === 0, conflicts };
5104
+ }
5105
+ parseICalEvent(data, url) {
5106
+ try {
5107
+ const lines = data.split("\n").map((l) => l.trim());
5108
+ const get = (key) => lines.find((l) => l.startsWith(key + ":"))?.slice(key.length + 1);
5109
+ const summary = get("SUMMARY");
5110
+ const dtstart = get("DTSTART") ?? get("DTSTART;VALUE=DATE");
5111
+ const dtend = get("DTEND") ?? get("DTEND;VALUE=DATE");
5112
+ const location = get("LOCATION");
5113
+ const description = get("DESCRIPTION");
5114
+ const uid = get("UID") ?? url;
5115
+ if (!summary || !dtstart)
5116
+ return void 0;
5117
+ const allDay = dtstart.length === 8;
5118
+ return {
5119
+ id: uid,
5120
+ title: summary,
5121
+ start: this.parseICalDate(dtstart),
5122
+ end: dtend ? this.parseICalDate(dtend) : this.parseICalDate(dtstart),
5123
+ location: location || void 0,
5124
+ description: description || void 0,
5125
+ allDay
5126
+ };
5127
+ } catch {
5128
+ return void 0;
5129
+ }
5130
+ }
5131
+ parseICalDate(str) {
5132
+ if (str.length === 8) {
5133
+ return /* @__PURE__ */ new Date(`${str.slice(0, 4)}-${str.slice(4, 6)}-${str.slice(6, 8)}`);
5134
+ }
5135
+ const clean = str.replace(/[^0-9TZ]/g, "");
5136
+ if (clean.length >= 15) {
5137
+ return /* @__PURE__ */ new Date(`${clean.slice(0, 4)}-${clean.slice(4, 6)}-${clean.slice(6, 8)}T${clean.slice(9, 11)}:${clean.slice(11, 13)}:${clean.slice(13, 15)}Z`);
5138
+ }
5139
+ return new Date(str);
5140
+ }
5141
+ buildICalEvent(uid, input2) {
5142
+ const formatDate = (d, allDay) => {
5143
+ if (allDay) {
5144
+ return d.toISOString().slice(0, 10).replace(/-/g, "");
5145
+ }
5146
+ return d.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}/, "");
5147
+ };
5148
+ let ical = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Alfred//EN\r\nBEGIN:VEVENT\r\n";
5149
+ ical += `UID:${uid}\r
5150
+ `;
5151
+ ical += `SUMMARY:${input2.title}\r
5152
+ `;
5153
+ if (input2.allDay) {
5154
+ ical += `DTSTART;VALUE=DATE:${formatDate(input2.start, true)}\r
5155
+ `;
5156
+ ical += `DTEND;VALUE=DATE:${formatDate(input2.end, true)}\r
5157
+ `;
5158
+ } else {
5159
+ ical += `DTSTART:${formatDate(input2.start)}\r
5160
+ `;
5161
+ ical += `DTEND:${formatDate(input2.end)}\r
5162
+ `;
5163
+ }
5164
+ if (input2.location)
5165
+ ical += `LOCATION:${input2.location}\r
5166
+ `;
5167
+ if (input2.description)
5168
+ ical += `DESCRIPTION:${input2.description}\r
5169
+ `;
5170
+ ical += `DTSTAMP:${formatDate(/* @__PURE__ */ new Date())}\r
5171
+ `;
5172
+ ical += "END:VEVENT\r\nEND:VCALENDAR\r\n";
5173
+ return ical;
5174
+ }
5175
+ };
5176
+ }
5177
+ });
5178
+
5179
+ // ../skills/dist/built-in/calendar/google-provider.js
5180
+ var google_provider_exports = {};
5181
+ __export(google_provider_exports, {
5182
+ GoogleCalendarProvider: () => GoogleCalendarProvider
5183
+ });
5184
+ var GoogleCalendarProvider;
5185
+ var init_google_provider = __esm({
5186
+ "../skills/dist/built-in/calendar/google-provider.js"() {
5187
+ "use strict";
5188
+ init_calendar_provider();
5189
+ GoogleCalendarProvider = class extends CalendarProvider {
5190
+ config;
5191
+ calendar;
5192
+ constructor(config) {
5193
+ super();
5194
+ this.config = config;
5195
+ }
5196
+ async initialize() {
5197
+ try {
5198
+ const { google } = await import("googleapis");
5199
+ const auth = new google.auth.OAuth2(this.config.clientId, this.config.clientSecret);
5200
+ auth.setCredentials({ refresh_token: this.config.refreshToken });
5201
+ this.calendar = google.calendar({ version: "v3", auth });
5202
+ } catch (err) {
5203
+ throw new Error(`Google Calendar initialization failed: ${err instanceof Error ? err.message : String(err)}`);
5204
+ }
5205
+ }
5206
+ async listEvents(start, end) {
5207
+ const response = await this.calendar.events.list({
5208
+ calendarId: "primary",
5209
+ timeMin: start.toISOString(),
5210
+ timeMax: end.toISOString(),
5211
+ singleEvents: true,
5212
+ orderBy: "startTime"
5213
+ });
5214
+ return (response.data.items ?? []).map((item) => this.mapEvent(item));
5215
+ }
5216
+ async createEvent(input2) {
5217
+ const event = {
5218
+ summary: input2.title,
5219
+ location: input2.location,
5220
+ description: input2.description
5221
+ };
5222
+ if (input2.allDay) {
5223
+ event.start = { date: input2.start.toISOString().slice(0, 10) };
5224
+ event.end = { date: input2.end.toISOString().slice(0, 10) };
5225
+ } else {
5226
+ event.start = { dateTime: input2.start.toISOString() };
5227
+ event.end = { dateTime: input2.end.toISOString() };
5228
+ }
5229
+ const response = await this.calendar.events.insert({
5230
+ calendarId: "primary",
5231
+ requestBody: event
5232
+ });
5233
+ return this.mapEvent(response.data);
5234
+ }
5235
+ async updateEvent(id, input2) {
5236
+ const patch = {};
5237
+ if (input2.title)
5238
+ patch.summary = input2.title;
5239
+ if (input2.location)
5240
+ patch.location = input2.location;
5241
+ if (input2.description)
5242
+ patch.description = input2.description;
5243
+ if (input2.start) {
5244
+ patch.start = input2.allDay ? { date: input2.start.toISOString().slice(0, 10) } : { dateTime: input2.start.toISOString() };
5245
+ }
5246
+ if (input2.end) {
5247
+ patch.end = input2.allDay ? { date: input2.end.toISOString().slice(0, 10) } : { dateTime: input2.end.toISOString() };
5248
+ }
5249
+ const response = await this.calendar.events.patch({
5250
+ calendarId: "primary",
5251
+ eventId: id,
5252
+ requestBody: patch
5253
+ });
5254
+ return this.mapEvent(response.data);
5255
+ }
5256
+ async deleteEvent(id) {
5257
+ await this.calendar.events.delete({
5258
+ calendarId: "primary",
5259
+ eventId: id
5260
+ });
5261
+ }
5262
+ async checkAvailability(start, end) {
5263
+ const events = await this.listEvents(start, end);
5264
+ const conflicts = events.filter((e) => !e.allDay && e.start < end && e.end > start);
5265
+ return { available: conflicts.length === 0, conflicts };
5266
+ }
5267
+ mapEvent(item) {
5268
+ const allDay = !!item.start?.date;
5269
+ return {
5270
+ id: item.id,
5271
+ title: item.summary ?? "(No title)",
5272
+ start: new Date(item.start?.dateTime ?? item.start?.date),
5273
+ end: new Date(item.end?.dateTime ?? item.end?.date),
5274
+ location: item.location ?? void 0,
5275
+ description: item.description ?? void 0,
5276
+ allDay
5277
+ };
5278
+ }
5279
+ };
5280
+ }
5281
+ });
5282
+
5283
+ // ../skills/dist/built-in/calendar/microsoft-provider.js
5284
+ var microsoft_provider_exports = {};
5285
+ __export(microsoft_provider_exports, {
5286
+ MicrosoftCalendarProvider: () => MicrosoftCalendarProvider
5287
+ });
5288
+ var MicrosoftCalendarProvider;
5289
+ var init_microsoft_provider = __esm({
5290
+ "../skills/dist/built-in/calendar/microsoft-provider.js"() {
5291
+ "use strict";
5292
+ init_calendar_provider();
5293
+ MicrosoftCalendarProvider = class extends CalendarProvider {
5294
+ config;
5295
+ client;
5296
+ accessToken = "";
5297
+ constructor(config) {
5298
+ super();
5299
+ this.config = config;
5300
+ }
5301
+ async initialize() {
5302
+ await this.refreshAccessToken();
5303
+ }
5304
+ async refreshAccessToken() {
5305
+ const tokenUrl = `https://login.microsoftonline.com/${this.config.tenantId}/oauth2/v2.0/token`;
5306
+ const body = new URLSearchParams({
5307
+ client_id: this.config.clientId,
5308
+ client_secret: this.config.clientSecret,
5309
+ refresh_token: this.config.refreshToken,
5310
+ grant_type: "refresh_token",
5311
+ scope: "https://graph.microsoft.com/Calendars.ReadWrite offline_access"
5312
+ });
5313
+ const res = await fetch(tokenUrl, {
5314
+ method: "POST",
5315
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
5316
+ body: body.toString()
5317
+ });
5318
+ if (!res.ok) {
5319
+ throw new Error(`Microsoft token refresh failed: ${res.status}`);
5320
+ }
5321
+ const data = await res.json();
5322
+ this.accessToken = data.access_token;
5323
+ }
5324
+ async graphRequest(path13, options = {}) {
5325
+ const res = await fetch(`https://graph.microsoft.com/v1.0${path13}`, {
5326
+ ...options,
5327
+ headers: {
5328
+ Authorization: `Bearer ${this.accessToken}`,
5329
+ "Content-Type": "application/json",
5330
+ ...options.headers
5331
+ }
5332
+ });
5333
+ if (res.status === 401) {
5334
+ await this.refreshAccessToken();
5335
+ const retry = await fetch(`https://graph.microsoft.com/v1.0${path13}`, {
5336
+ ...options,
5337
+ headers: {
5338
+ Authorization: `Bearer ${this.accessToken}`,
5339
+ "Content-Type": "application/json",
5340
+ ...options.headers
5341
+ }
5342
+ });
5343
+ if (!retry.ok)
5344
+ throw new Error(`Graph API error: ${retry.status}`);
5345
+ return retry.json();
5346
+ }
5347
+ if (!res.ok)
5348
+ throw new Error(`Graph API error: ${res.status}`);
5349
+ if (res.status === 204)
5350
+ return void 0;
5351
+ return res.json();
5352
+ }
5353
+ async listEvents(start, end) {
5354
+ const params = new URLSearchParams({
5355
+ startDateTime: start.toISOString(),
5356
+ endDateTime: end.toISOString(),
5357
+ $orderby: "start/dateTime",
5358
+ $top: "50"
5359
+ });
5360
+ const data = await this.graphRequest(`/me/calendarView?${params}`);
5361
+ return (data.value ?? []).map((item) => this.mapEvent(item));
5362
+ }
5363
+ async createEvent(input2) {
5364
+ const event = {
5365
+ subject: input2.title,
5366
+ body: input2.description ? { contentType: "text", content: input2.description } : void 0,
5367
+ location: input2.location ? { displayName: input2.location } : void 0,
5368
+ isAllDay: input2.allDay ?? false
5369
+ };
5370
+ if (input2.allDay) {
5371
+ event.start = { dateTime: input2.start.toISOString().slice(0, 10) + "T00:00:00", timeZone: "UTC" };
5372
+ event.end = { dateTime: input2.end.toISOString().slice(0, 10) + "T00:00:00", timeZone: "UTC" };
5373
+ } else {
5374
+ event.start = { dateTime: input2.start.toISOString(), timeZone: "UTC" };
5375
+ event.end = { dateTime: input2.end.toISOString(), timeZone: "UTC" };
5376
+ }
5377
+ const data = await this.graphRequest("/me/events", {
5378
+ method: "POST",
5379
+ body: JSON.stringify(event)
5380
+ });
5381
+ return this.mapEvent(data);
5382
+ }
5383
+ async updateEvent(id, input2) {
5384
+ const patch = {};
5385
+ if (input2.title)
5386
+ patch.subject = input2.title;
5387
+ if (input2.description)
5388
+ patch.body = { contentType: "text", content: input2.description };
5389
+ if (input2.location)
5390
+ patch.location = { displayName: input2.location };
5391
+ if (input2.start) {
5392
+ patch.start = { dateTime: input2.start.toISOString(), timeZone: "UTC" };
5393
+ }
5394
+ if (input2.end) {
5395
+ patch.end = { dateTime: input2.end.toISOString(), timeZone: "UTC" };
5396
+ }
5397
+ const data = await this.graphRequest(`/me/events/${id}`, {
5398
+ method: "PATCH",
5399
+ body: JSON.stringify(patch)
5400
+ });
5401
+ return this.mapEvent(data);
5402
+ }
5403
+ async deleteEvent(id) {
5404
+ await this.graphRequest(`/me/events/${id}`, { method: "DELETE" });
5405
+ }
5406
+ async checkAvailability(start, end) {
5407
+ const events = await this.listEvents(start, end);
5408
+ const conflicts = events.filter((e) => !e.allDay && e.start < end && e.end > start);
5409
+ return { available: conflicts.length === 0, conflicts };
5410
+ }
5411
+ mapEvent(item) {
5412
+ return {
5413
+ id: item.id,
5414
+ title: item.subject ?? "(No title)",
5415
+ start: new Date(item.start?.dateTime),
5416
+ end: new Date(item.end?.dateTime),
5417
+ location: item.location?.displayName ?? void 0,
5418
+ description: item.body?.content ?? void 0,
5419
+ allDay: item.isAllDay ?? false
5420
+ };
5421
+ }
5422
+ };
5423
+ }
5424
+ });
5425
+
5426
+ // ../skills/dist/built-in/calendar/factory.js
5427
+ async function createCalendarProvider(config) {
5428
+ switch (config.provider) {
5429
+ case "caldav": {
5430
+ if (!config.caldav)
5431
+ throw new Error("CalDAV config missing");
5432
+ const { CalDAVProvider: CalDAVProvider2 } = await Promise.resolve().then(() => (init_caldav_provider(), caldav_provider_exports));
5433
+ const provider = new CalDAVProvider2(config.caldav);
5434
+ await provider.initialize();
5435
+ return provider;
5436
+ }
5437
+ case "google": {
5438
+ if (!config.google)
5439
+ throw new Error("Google Calendar config missing");
5440
+ const { GoogleCalendarProvider: GoogleCalendarProvider2 } = await Promise.resolve().then(() => (init_google_provider(), google_provider_exports));
5441
+ const provider = new GoogleCalendarProvider2(config.google);
5442
+ await provider.initialize();
5443
+ return provider;
5444
+ }
5445
+ case "microsoft": {
5446
+ if (!config.microsoft)
5447
+ throw new Error("Microsoft Calendar config missing");
5448
+ const { MicrosoftCalendarProvider: MicrosoftCalendarProvider2 } = await Promise.resolve().then(() => (init_microsoft_provider(), microsoft_provider_exports));
5449
+ const provider = new MicrosoftCalendarProvider2(config.microsoft);
5450
+ await provider.initialize();
5451
+ return provider;
5452
+ }
5453
+ default:
5454
+ throw new Error(`Unknown calendar provider: ${config.provider}`);
5455
+ }
5456
+ }
5457
+ var init_factory = __esm({
5458
+ "../skills/dist/built-in/calendar/factory.js"() {
5459
+ "use strict";
5460
+ }
5461
+ });
5462
+
5463
+ // ../skills/dist/built-in/calendar/calendar-skill.js
5464
+ var CalendarSkill;
5465
+ var init_calendar_skill = __esm({
5466
+ "../skills/dist/built-in/calendar/calendar-skill.js"() {
5467
+ "use strict";
5468
+ init_skill();
5469
+ CalendarSkill = class extends Skill {
5470
+ calendarProvider;
5471
+ timezone;
5472
+ metadata = {
5473
+ name: "calendar",
5474
+ description: "Manage calendar events. List upcoming events, create new events, update or delete existing ones, and check availability.",
5475
+ riskLevel: "write",
5476
+ version: "1.0.0",
5477
+ inputSchema: {
5478
+ type: "object",
5479
+ properties: {
5480
+ action: {
5481
+ type: "string",
5482
+ enum: ["list_events", "create_event", "update_event", "delete_event", "check_availability"],
5483
+ description: "The calendar action to perform"
5484
+ },
5485
+ start: {
5486
+ type: "string",
5487
+ description: "Start date/time in ISO 8601 format"
5488
+ },
5489
+ end: {
5490
+ type: "string",
5491
+ description: "End date/time in ISO 8601 format"
5492
+ },
5493
+ title: {
5494
+ type: "string",
5495
+ description: "Event title (for create/update)"
5496
+ },
5497
+ location: {
5498
+ type: "string",
5499
+ description: "Event location (for create/update)"
5500
+ },
5501
+ description: {
5502
+ type: "string",
5503
+ description: "Event description (for create/update)"
5504
+ },
5505
+ event_id: {
5506
+ type: "string",
5507
+ description: "Event ID (for update/delete)"
5508
+ },
5509
+ all_day: {
5510
+ type: "boolean",
5511
+ description: "Whether this is an all-day event"
5512
+ }
5513
+ },
5514
+ required: ["action"]
5515
+ }
5516
+ };
5517
+ constructor(calendarProvider, timezone) {
5518
+ super();
5519
+ this.calendarProvider = calendarProvider;
5520
+ this.timezone = timezone;
5521
+ }
5522
+ async execute(input2, _context) {
5523
+ const action = input2.action;
5524
+ switch (action) {
5525
+ case "list_events":
5526
+ return this.listEvents(input2);
5527
+ case "create_event":
5528
+ return this.createEvent(input2);
5529
+ case "update_event":
5530
+ return this.updateEvent(input2);
5531
+ case "delete_event":
5532
+ return this.deleteEvent(input2);
5533
+ case "check_availability":
5534
+ return this.checkAvailability(input2);
5535
+ default:
5536
+ return { success: false, error: `Unknown action: "${String(action)}"` };
5537
+ }
5538
+ }
5539
+ async getTodayEvents() {
5540
+ const now = /* @__PURE__ */ new Date();
5541
+ const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
5542
+ const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
5543
+ try {
5544
+ return await this.calendarProvider.listEvents(startOfDay, endOfDay);
5545
+ } catch {
5546
+ return [];
5547
+ }
5548
+ }
5549
+ async listEvents(input2) {
5550
+ const start = input2.start ? new Date(input2.start) : /* @__PURE__ */ new Date();
5551
+ const end = input2.end ? new Date(input2.end) : new Date(start.getTime() + 7 * 24 * 60 * 60 * 1e3);
5552
+ try {
5553
+ const events = await this.calendarProvider.listEvents(start, end);
5554
+ if (events.length === 0) {
5555
+ return { success: true, data: [], display: "No events found in this time range." };
5556
+ }
5557
+ const display = events.map((e) => this.formatEvent(e)).join("\n");
5558
+ return { success: true, data: events, display: `${events.length} event(s):
5559
+ ${display}` };
5560
+ } catch (err) {
5561
+ return { success: false, error: `Failed to list events: ${err instanceof Error ? err.message : String(err)}` };
5562
+ }
5563
+ }
5564
+ async createEvent(input2) {
5565
+ const title = input2.title;
5566
+ const start = input2.start;
5567
+ const end = input2.end;
5568
+ if (!title)
5569
+ return { success: false, error: 'Missing required field "title"' };
5570
+ if (!start)
5571
+ return { success: false, error: 'Missing required field "start"' };
5572
+ if (!end)
5573
+ return { success: false, error: 'Missing required field "end"' };
5574
+ try {
5575
+ const event = await this.calendarProvider.createEvent({
5576
+ title,
5577
+ start: new Date(start),
5578
+ end: new Date(end),
5579
+ location: input2.location,
5580
+ description: input2.description,
5581
+ allDay: input2.all_day
5582
+ });
5583
+ return {
5584
+ success: true,
5585
+ data: event,
5586
+ display: `Event created: ${this.formatEvent(event)}`
5587
+ };
5588
+ } catch (err) {
5589
+ return { success: false, error: `Failed to create event: ${err instanceof Error ? err.message : String(err)}` };
5590
+ }
5591
+ }
5592
+ async updateEvent(input2) {
5593
+ const eventId = input2.event_id;
5594
+ if (!eventId)
5595
+ return { success: false, error: 'Missing required field "event_id"' };
5596
+ try {
5597
+ const event = await this.calendarProvider.updateEvent(eventId, {
5598
+ title: input2.title,
5599
+ start: input2.start ? new Date(input2.start) : void 0,
5600
+ end: input2.end ? new Date(input2.end) : void 0,
5601
+ location: input2.location,
5602
+ description: input2.description,
5603
+ allDay: input2.all_day
5604
+ });
5605
+ return {
5606
+ success: true,
5607
+ data: event,
5608
+ display: `Event updated: ${this.formatEvent(event)}`
5609
+ };
5610
+ } catch (err) {
5611
+ return { success: false, error: `Failed to update event: ${err instanceof Error ? err.message : String(err)}` };
5612
+ }
5613
+ }
5614
+ async deleteEvent(input2) {
5615
+ const eventId = input2.event_id;
5616
+ if (!eventId)
5617
+ return { success: false, error: 'Missing required field "event_id"' };
5618
+ try {
5619
+ await this.calendarProvider.deleteEvent(eventId);
5620
+ return { success: true, data: { deleted: eventId }, display: `Event "${eventId}" deleted.` };
5621
+ } catch (err) {
5622
+ return { success: false, error: `Failed to delete event: ${err instanceof Error ? err.message : String(err)}` };
5623
+ }
5624
+ }
5625
+ async checkAvailability(input2) {
5626
+ const start = input2.start;
5627
+ const end = input2.end;
5628
+ if (!start || !end)
5629
+ return { success: false, error: 'Missing required fields "start" and "end"' };
5630
+ try {
5631
+ const result = await this.calendarProvider.checkAvailability(new Date(start), new Date(end));
5632
+ const display = result.available ? "Time slot is available." : `Time slot has ${result.conflicts.length} conflict(s):
5633
+ ${result.conflicts.map((e) => this.formatEvent(e)).join("\n")}`;
5634
+ return { success: true, data: result, display };
5635
+ } catch (err) {
5636
+ return { success: false, error: `Failed to check availability: ${err instanceof Error ? err.message : String(err)}` };
5637
+ }
5638
+ }
5639
+ formatEvent(event) {
5640
+ const opts = {
5641
+ hour: "2-digit",
5642
+ minute: "2-digit",
5643
+ ...this.timezone ? { timeZone: this.timezone } : {}
5644
+ };
5645
+ if (event.allDay) {
5646
+ return `- All day: ${event.title}${event.location ? ` @ ${event.location}` : ""}`;
5647
+ }
5648
+ const startTime = event.start.toLocaleTimeString("en-GB", opts);
5649
+ const endTime = event.end.toLocaleTimeString("en-GB", opts);
5650
+ return `- ${startTime}-${endTime}: ${event.title}${event.location ? ` @ ${event.location}` : ""}`;
5651
+ }
5652
+ };
5653
+ }
5654
+ });
5655
+
5656
+ // ../skills/dist/built-in/calendar/index.js
5657
+ var init_calendar = __esm({
5658
+ "../skills/dist/built-in/calendar/index.js"() {
5659
+ "use strict";
5660
+ init_calendar_provider();
5661
+ init_caldav_provider();
5662
+ init_google_provider();
5663
+ init_microsoft_provider();
5664
+ init_factory();
5665
+ init_calendar_skill();
5666
+ }
5667
+ });
5668
+
5669
+ // ../skills/dist/index.js
5670
+ var init_dist6 = __esm({
5671
+ "../skills/dist/index.js"() {
5672
+ "use strict";
5673
+ init_skill();
5674
+ init_skill_registry();
5675
+ init_skill_sandbox();
5676
+ init_plugin_loader();
5677
+ init_calculator();
5678
+ init_system_info();
5679
+ init_web_search();
5680
+ init_reminder();
5681
+ init_note();
5682
+ init_weather();
5683
+ init_shell();
5684
+ init_memory();
5685
+ init_delegate();
5686
+ init_email();
5687
+ init_http();
5688
+ init_file();
5689
+ init_clipboard();
5690
+ init_screenshot();
5691
+ init_browser();
5692
+ init_profile();
5693
+ init_calendar();
5694
+ }
5695
+ });
5696
+
5697
+ // ../core/dist/conversation-manager.js
5698
+ var ConversationManager;
5699
+ var init_conversation_manager = __esm({
5700
+ "../core/dist/conversation-manager.js"() {
5701
+ "use strict";
5702
+ ConversationManager = class {
5703
+ conversations;
5704
+ constructor(conversations) {
5705
+ this.conversations = conversations;
5706
+ }
5707
+ getOrCreateConversation(platform, chatId, userId) {
5708
+ const existing = this.conversations.findByPlatformChat(platform, chatId);
5709
+ if (existing) {
5710
+ this.conversations.updateTimestamp(existing.id);
5711
+ return existing;
5712
+ }
5713
+ return this.conversations.create(platform, chatId, userId);
5714
+ }
5715
+ addMessage(conversationId, role, content, toolCalls) {
5716
+ return this.conversations.addMessage(conversationId, role, content, toolCalls);
5717
+ }
5718
+ getHistory(conversationId, limit = 20) {
5719
+ return this.conversations.getMessages(conversationId, limit);
5720
+ }
5721
+ };
5722
+ }
5723
+ });
5724
+
5725
+ // ../core/dist/message-pipeline.js
5726
+ import fs5 from "node:fs";
5727
+ import path7 from "node:path";
5728
+ var MAX_TOOL_ITERATIONS, TOKEN_BUDGET_RATIO, MAX_INLINE_FILE_SIZE, MessagePipeline;
5729
+ var init_message_pipeline = __esm({
5730
+ "../core/dist/message-pipeline.js"() {
4486
5731
  "use strict";
4487
5732
  init_dist4();
4488
5733
  MAX_TOOL_ITERATIONS = 10;
4489
5734
  TOKEN_BUDGET_RATIO = 0.85;
5735
+ MAX_INLINE_FILE_SIZE = 1e5;
4490
5736
  MessagePipeline = class {
5737
+ promptBuilder;
4491
5738
  llm;
4492
5739
  conversationManager;
4493
5740
  users;
@@ -4496,16 +5743,21 @@ var init_message_pipeline = __esm({
4496
5743
  skillSandbox;
4497
5744
  securityManager;
4498
5745
  memoryRepo;
4499
- promptBuilder;
4500
- constructor(llm, conversationManager, users, logger, skillRegistry, skillSandbox, securityManager, memoryRepo) {
4501
- this.llm = llm;
4502
- this.conversationManager = conversationManager;
4503
- this.users = users;
4504
- this.logger = logger;
4505
- this.skillRegistry = skillRegistry;
4506
- this.skillSandbox = skillSandbox;
4507
- this.securityManager = securityManager;
4508
- this.memoryRepo = memoryRepo;
5746
+ speechTranscriber;
5747
+ inboxPath;
5748
+ embeddingService;
5749
+ constructor(options) {
5750
+ this.llm = options.llm;
5751
+ this.conversationManager = options.conversationManager;
5752
+ this.users = options.users;
5753
+ this.logger = options.logger;
5754
+ this.skillRegistry = options.skillRegistry;
5755
+ this.skillSandbox = options.skillSandbox;
5756
+ this.securityManager = options.securityManager;
5757
+ this.memoryRepo = options.memoryRepo;
5758
+ this.speechTranscriber = options.speechTranscriber;
5759
+ this.inboxPath = options.inboxPath;
5760
+ this.embeddingService = options.embeddingService;
4509
5761
  this.promptBuilder = new PromptBuilder();
4510
5762
  }
4511
5763
  async process(message, onProgress) {
@@ -4519,15 +5771,49 @@ var init_message_pipeline = __esm({
4519
5771
  let memories;
4520
5772
  if (this.memoryRepo) {
4521
5773
  try {
4522
- memories = this.memoryRepo.getRecentForPrompt(user.id, 20);
5774
+ if (this.embeddingService && message.text) {
5775
+ const semanticResults = await this.embeddingService.semanticSearch(user.id, message.text, 10);
5776
+ const recentResults = this.memoryRepo.getRecentForPrompt(user.id, 5);
5777
+ const seen = /* @__PURE__ */ new Set();
5778
+ memories = [];
5779
+ for (const m of semanticResults) {
5780
+ if (!seen.has(m.key)) {
5781
+ seen.add(m.key);
5782
+ memories.push(m);
5783
+ }
5784
+ }
5785
+ for (const m of recentResults) {
5786
+ if (!seen.has(m.key)) {
5787
+ seen.add(m.key);
5788
+ memories.push(m);
5789
+ }
5790
+ }
5791
+ } else {
5792
+ memories = this.memoryRepo.getRecentForPrompt(user.id, 20);
5793
+ }
4523
5794
  } catch {
4524
5795
  }
4525
5796
  }
5797
+ let userProfile;
5798
+ try {
5799
+ if ("getProfile" in this.users) {
5800
+ userProfile = this.users.getProfile(user.id);
5801
+ if (userProfile && !userProfile.displayName) {
5802
+ userProfile.displayName = user.displayName ?? user.username;
5803
+ }
5804
+ }
5805
+ } catch {
5806
+ }
4526
5807
  const skillMetas = this.skillRegistry ? this.skillRegistry.getAll().map((s) => s.metadata) : void 0;
4527
5808
  const tools = skillMetas ? this.promptBuilder.buildTools(skillMetas) : void 0;
4528
- const system = this.promptBuilder.buildSystemPrompt(memories, skillMetas);
5809
+ const system = this.promptBuilder.buildSystemPrompt({
5810
+ memories,
5811
+ skills: skillMetas,
5812
+ userProfile
5813
+ });
4529
5814
  const allMessages = this.promptBuilder.buildMessages(history);
4530
- allMessages.push({ role: "user", content: message.text });
5815
+ const userContent = await this.buildUserContent(message, onProgress);
5816
+ allMessages.push({ role: "user", content: userContent });
4531
5817
  const messages = this.trimToContextWindow(system, allMessages);
4532
5818
  let response;
4533
5819
  let iteration = 0;
@@ -4672,6 +5958,10 @@ var init_message_pipeline = __esm({
4672
5958
  return `Weather: ${String(input2.location ?? "")}`;
4673
5959
  case "note":
4674
5960
  return `Note: ${String(input2.action ?? "")}`;
5961
+ case "profile":
5962
+ return `Profile: ${String(input2.action ?? "")}`;
5963
+ case "calendar":
5964
+ return `Calendar: ${String(input2.action ?? "")}`;
4675
5965
  default:
4676
5966
  return `Using ${toolName}...`;
4677
5967
  }
@@ -4713,6 +6003,135 @@ var init_message_pipeline = __esm({
4713
6003
  keptMessages.push(latestMsg);
4714
6004
  return keptMessages;
4715
6005
  }
6006
+ /**
6007
+ * Build the user content for the LLM request.
6008
+ * Handles images (as vision blocks), audio (transcribed via Whisper),
6009
+ * documents/files (saved to inbox), and plain text.
6010
+ */
6011
+ async buildUserContent(message, onProgress) {
6012
+ const attachments = message.attachments?.filter((a) => a.data) ?? [];
6013
+ if (attachments.length === 0) {
6014
+ return message.text;
6015
+ }
6016
+ const blocks = [];
6017
+ for (const attachment of attachments) {
6018
+ if (attachment.type === "image" && attachment.data) {
6019
+ blocks.push({
6020
+ type: "image",
6021
+ source: {
6022
+ type: "base64",
6023
+ media_type: attachment.mimeType ?? "image/jpeg",
6024
+ data: attachment.data.toString("base64")
6025
+ }
6026
+ });
6027
+ this.logger.info({ mimeType: attachment.mimeType, size: attachment.size }, "Image attached to LLM request");
6028
+ } else if (attachment.type === "audio" && attachment.data) {
6029
+ if (this.speechTranscriber) {
6030
+ onProgress?.("Transcribing voice...");
6031
+ try {
6032
+ const transcript = await this.speechTranscriber.transcribe(attachment.data, attachment.mimeType ?? "audio/ogg");
6033
+ const label = message.text === "[Voice message]" ? "" : `${message.text}
6034
+
6035
+ `;
6036
+ blocks.push({
6037
+ type: "text",
6038
+ text: `${label}[Voice transcript]: ${transcript}`
6039
+ });
6040
+ this.logger.info({ transcriptLength: transcript.length }, "Voice message transcribed");
6041
+ return blocks.length === 1 ? blocks[0].type === "text" ? blocks[0].text : blocks : blocks;
6042
+ } catch (err) {
6043
+ this.logger.error({ err }, "Voice transcription failed");
6044
+ blocks.push({
6045
+ type: "text",
6046
+ text: "[Voice message could not be transcribed]"
6047
+ });
6048
+ }
6049
+ } else {
6050
+ blocks.push({
6051
+ type: "text",
6052
+ text: "[Voice message received but speech-to-text is not configured. Add speech config to enable transcription.]"
6053
+ });
6054
+ }
6055
+ } else if ((attachment.type === "document" || attachment.type === "video" || attachment.type === "other") && attachment.data) {
6056
+ const savedPath = this.saveToInbox(attachment);
6057
+ if (savedPath) {
6058
+ const isTextFile = this.isTextMimeType(attachment.mimeType);
6059
+ let fileNote = `[File received: "${attachment.fileName ?? "unknown"}" (${this.formatBytes(attachment.data.length)}, ${attachment.mimeType ?? "unknown type"})]
6060
+ [Saved to: ${savedPath}]`;
6061
+ if (isTextFile && attachment.data.length <= MAX_INLINE_FILE_SIZE) {
6062
+ const textContent = attachment.data.toString("utf-8");
6063
+ fileNote += `
6064
+ [File content]:
6065
+ ${textContent}`;
6066
+ }
6067
+ blocks.push({ type: "text", text: fileNote });
6068
+ this.logger.info({ fileName: attachment.fileName, savedPath, size: attachment.data.length }, "File saved to inbox");
6069
+ }
6070
+ }
6071
+ }
6072
+ const skipTexts = ["[Photo]", "[Voice message]", "[Video]", "[Video note]", "[Document]", "[File]"];
6073
+ if (message.text && !skipTexts.includes(message.text)) {
6074
+ blocks.push({ type: "text", text: message.text });
6075
+ } else if (blocks.some((b) => b.type === "image") && !blocks.some((b) => b.type === "text")) {
6076
+ blocks.push({ type: "text", text: "What do you see in this image?" });
6077
+ } else if (blocks.length === 0) {
6078
+ blocks.push({ type: "text", text: message.text || "(empty message)" });
6079
+ }
6080
+ return blocks;
6081
+ }
6082
+ /**
6083
+ * Save an attachment to the inbox directory.
6084
+ * Returns the saved file path, or undefined on failure.
6085
+ */
6086
+ saveToInbox(attachment) {
6087
+ if (!attachment.data)
6088
+ return void 0;
6089
+ const inboxDir = this.inboxPath ?? path7.resolve("./data/inbox");
6090
+ try {
6091
+ fs5.mkdirSync(inboxDir, { recursive: true });
6092
+ } catch {
6093
+ this.logger.error({ inboxDir }, "Cannot create inbox directory");
6094
+ return void 0;
6095
+ }
6096
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
6097
+ const originalName = attachment.fileName ?? `file_${timestamp}`;
6098
+ const safeName = originalName.replace(/[<>:"/\\|?*]/g, "_");
6099
+ const fileName = `${timestamp}_${safeName}`;
6100
+ const filePath = path7.join(inboxDir, fileName);
6101
+ try {
6102
+ fs5.writeFileSync(filePath, attachment.data);
6103
+ return filePath;
6104
+ } catch (err) {
6105
+ this.logger.error({ err, filePath }, "Failed to save file to inbox");
6106
+ return void 0;
6107
+ }
6108
+ }
6109
+ isTextMimeType(mimeType) {
6110
+ if (!mimeType)
6111
+ return false;
6112
+ const textTypes = [
6113
+ "text/",
6114
+ "application/json",
6115
+ "application/xml",
6116
+ "application/javascript",
6117
+ "application/typescript",
6118
+ "application/x-yaml",
6119
+ "application/yaml",
6120
+ "application/toml",
6121
+ "application/x-sh",
6122
+ "application/sql",
6123
+ "application/csv",
6124
+ "application/x-csv"
6125
+ ];
6126
+ return textTypes.some((t) => mimeType.startsWith(t));
6127
+ }
6128
+ formatBytes(bytes) {
6129
+ if (bytes < 1024)
6130
+ return `${bytes} B`;
6131
+ if (bytes < 1024 * 1024)
6132
+ return `${(bytes / 1024).toFixed(1)} KB`;
6133
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
6134
+ }
4716
6135
  };
4717
6136
  }
4718
6137
  });
@@ -4732,35 +6151,239 @@ var init_reminder_scheduler = __esm({
4732
6151
  this.reminderRepo = reminderRepo;
4733
6152
  this.sendMessage = sendMessage;
4734
6153
  this.logger = logger;
4735
- this.checkIntervalMs = checkIntervalMs;
4736
- }
4737
- start() {
4738
- this.logger.info("Reminder scheduler started");
4739
- this.intervalId = setInterval(() => this.checkDueReminders(), this.checkIntervalMs);
4740
- this.checkDueReminders();
6154
+ this.checkIntervalMs = checkIntervalMs;
6155
+ }
6156
+ start() {
6157
+ this.logger.info("Reminder scheduler started");
6158
+ this.intervalId = setInterval(() => this.checkDueReminders(), this.checkIntervalMs);
6159
+ this.checkDueReminders();
6160
+ }
6161
+ stop() {
6162
+ if (this.intervalId) {
6163
+ clearInterval(this.intervalId);
6164
+ this.intervalId = void 0;
6165
+ }
6166
+ this.logger.info("Reminder scheduler stopped");
6167
+ }
6168
+ async checkDueReminders() {
6169
+ try {
6170
+ const due = this.reminderRepo.getDue();
6171
+ for (const reminder of due) {
6172
+ try {
6173
+ await this.sendMessage(reminder.platform, reminder.chatId, `\u23F0 Reminder: ${reminder.message}`);
6174
+ this.reminderRepo.markFired(reminder.id);
6175
+ this.logger.info({ reminderId: reminder.id }, "Reminder fired");
6176
+ } catch (err) {
6177
+ this.logger.error({ err, reminderId: reminder.id }, "Failed to send reminder");
6178
+ }
6179
+ }
6180
+ } catch (err) {
6181
+ this.logger.error({ err }, "Error checking due reminders");
6182
+ }
6183
+ }
6184
+ };
6185
+ }
6186
+ });
6187
+
6188
+ // ../core/dist/speech-transcriber.js
6189
+ var SpeechTranscriber;
6190
+ var init_speech_transcriber = __esm({
6191
+ "../core/dist/speech-transcriber.js"() {
6192
+ "use strict";
6193
+ SpeechTranscriber = class {
6194
+ logger;
6195
+ apiKey;
6196
+ baseUrl;
6197
+ constructor(config, logger) {
6198
+ this.logger = logger;
6199
+ this.apiKey = config.apiKey;
6200
+ if (config.provider === "groq") {
6201
+ this.baseUrl = config.baseUrl ?? "https://api.groq.com/openai/v1";
6202
+ } else {
6203
+ this.baseUrl = config.baseUrl ?? "https://api.openai.com/v1";
6204
+ }
6205
+ }
6206
+ async transcribe(audioBuffer, mimeType) {
6207
+ const ext = this.mimeToExtension(mimeType);
6208
+ const formData = new FormData();
6209
+ formData.append("file", new Blob([audioBuffer], { type: mimeType }), `audio.${ext}`);
6210
+ formData.append("model", "whisper-1");
6211
+ try {
6212
+ const response = await fetch(`${this.baseUrl}/audio/transcriptions`, {
6213
+ method: "POST",
6214
+ headers: {
6215
+ "Authorization": `Bearer ${this.apiKey}`
6216
+ },
6217
+ body: formData
6218
+ });
6219
+ if (!response.ok) {
6220
+ const errorText = await response.text();
6221
+ throw new Error(`Whisper API ${response.status}: ${errorText}`);
6222
+ }
6223
+ const data = await response.json();
6224
+ this.logger.info({ textLength: data.text.length }, "Voice transcribed");
6225
+ return data.text;
6226
+ } catch (err) {
6227
+ this.logger.error({ err }, "Voice transcription failed");
6228
+ throw err;
6229
+ }
6230
+ }
6231
+ mimeToExtension(mimeType) {
6232
+ const map = {
6233
+ "audio/ogg": "ogg",
6234
+ "audio/mpeg": "mp3",
6235
+ "audio/mp4": "m4a",
6236
+ "audio/wav": "wav",
6237
+ "audio/webm": "webm",
6238
+ "audio/x-m4a": "m4a"
6239
+ };
6240
+ return map[mimeType] ?? "ogg";
6241
+ }
6242
+ };
6243
+ }
6244
+ });
6245
+
6246
+ // ../core/dist/response-formatter.js
6247
+ var ResponseFormatter;
6248
+ var init_response_formatter = __esm({
6249
+ "../core/dist/response-formatter.js"() {
6250
+ "use strict";
6251
+ ResponseFormatter = class {
6252
+ format(text, platform) {
6253
+ switch (platform) {
6254
+ case "telegram":
6255
+ return { text: this.toTelegramHTML(text), parseMode: "html" };
6256
+ case "discord":
6257
+ return { text, parseMode: "markdown" };
6258
+ case "matrix":
6259
+ return { text: this.toMatrixHTML(text), parseMode: "html" };
6260
+ case "whatsapp":
6261
+ return { text: this.toWhatsApp(text), parseMode: "text" };
6262
+ case "signal":
6263
+ return { text: this.stripFormatting(text), parseMode: "text" };
6264
+ default:
6265
+ return { text, parseMode: "text" };
6266
+ }
6267
+ }
6268
+ toTelegramHTML(md) {
6269
+ let html = md;
6270
+ html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, _lang, code) => {
6271
+ return `<pre>${this.escapeHTML(code.trimEnd())}</pre>`;
6272
+ });
6273
+ html = html.replace(/`([^`]+)`/g, (_match, code) => {
6274
+ return `<code>${this.escapeHTML(code)}</code>`;
6275
+ });
6276
+ html = html.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
6277
+ html = html.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "<i>$1</i>");
6278
+ html = html.replace(/~~(.+?)~~/g, "<s>$1</s>");
6279
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
6280
+ return html;
6281
+ }
6282
+ toMatrixHTML(md) {
6283
+ return this.toTelegramHTML(md);
6284
+ }
6285
+ toWhatsApp(md) {
6286
+ let text = md;
6287
+ text = text.replace(/\*\*(.+?)\*\*/g, "*$1*");
6288
+ text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "_$1_");
6289
+ text = text.replace(/~~(.+?)~~/g, "~$1~");
6290
+ text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
6291
+ return text;
6292
+ }
6293
+ stripFormatting(md) {
6294
+ let text = md;
6295
+ text = text.replace(/```\w*\n?/g, "");
6296
+ text = text.replace(/`([^`]+)`/g, "$1");
6297
+ text = text.replace(/\*\*(.+?)\*\*/g, "$1");
6298
+ text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "$1");
6299
+ text = text.replace(/~~(.+?)~~/g, "$1");
6300
+ text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
6301
+ return text;
6302
+ }
6303
+ escapeHTML(text) {
6304
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
6305
+ }
6306
+ };
6307
+ }
6308
+ });
6309
+
6310
+ // ../core/dist/embedding-service.js
6311
+ var EmbeddingService;
6312
+ var init_embedding_service = __esm({
6313
+ "../core/dist/embedding-service.js"() {
6314
+ "use strict";
6315
+ EmbeddingService = class {
6316
+ llm;
6317
+ embeddingRepo;
6318
+ logger;
6319
+ constructor(llm, embeddingRepo, logger) {
6320
+ this.llm = llm;
6321
+ this.embeddingRepo = embeddingRepo;
6322
+ this.logger = logger;
4741
6323
  }
4742
- stop() {
4743
- if (this.intervalId) {
4744
- clearInterval(this.intervalId);
4745
- this.intervalId = void 0;
6324
+ async embedAndStore(userId, content, sourceType, sourceId) {
6325
+ if (!this.llm.supportsEmbeddings()) {
6326
+ return;
6327
+ }
6328
+ try {
6329
+ const result = await this.llm.embed(content);
6330
+ if (!result)
6331
+ return;
6332
+ this.embeddingRepo.store({
6333
+ userId,
6334
+ sourceType,
6335
+ sourceId,
6336
+ content,
6337
+ embedding: result.embedding,
6338
+ model: result.model,
6339
+ dimensions: result.dimensions
6340
+ });
6341
+ this.logger.debug({ userId, sourceType, sourceId }, "Embedding stored");
6342
+ } catch (err) {
6343
+ this.logger.error({ err, userId, sourceType, sourceId }, "Failed to embed content");
4746
6344
  }
4747
- this.logger.info("Reminder scheduler stopped");
4748
6345
  }
4749
- async checkDueReminders() {
6346
+ async semanticSearch(userId, query, limit = 10) {
6347
+ if (!this.llm.supportsEmbeddings()) {
6348
+ return [];
6349
+ }
4750
6350
  try {
4751
- const due = this.reminderRepo.getDue();
4752
- for (const reminder of due) {
4753
- try {
4754
- await this.sendMessage(reminder.platform, reminder.chatId, `\u23F0 Reminder: ${reminder.message}`);
4755
- this.reminderRepo.markFired(reminder.id);
4756
- this.logger.info({ reminderId: reminder.id }, "Reminder fired");
4757
- } catch (err) {
4758
- this.logger.error({ err, reminderId: reminder.id }, "Failed to send reminder");
4759
- }
4760
- }
6351
+ const queryResult = await this.llm.embed(query);
6352
+ if (!queryResult)
6353
+ return [];
6354
+ const embeddings = this.embeddingRepo.findByUser(userId);
6355
+ if (embeddings.length === 0)
6356
+ return [];
6357
+ const scored = embeddings.map((entry) => {
6358
+ const score = this.cosineSimilarity(queryResult.embedding, entry.embedding);
6359
+ return { ...entry, score };
6360
+ });
6361
+ scored.sort((a, b) => b.score - a.score);
6362
+ const topResults = scored.slice(0, limit);
6363
+ return topResults.map((r) => ({
6364
+ key: r.sourceId,
6365
+ value: r.content,
6366
+ category: r.sourceType,
6367
+ score: r.score
6368
+ }));
4761
6369
  } catch (err) {
4762
- this.logger.error({ err }, "Error checking due reminders");
6370
+ this.logger.error({ err }, "Semantic search failed");
6371
+ return [];
6372
+ }
6373
+ }
6374
+ cosineSimilarity(a, b) {
6375
+ if (a.length !== b.length)
6376
+ return 0;
6377
+ let dotProduct = 0;
6378
+ let normA = 0;
6379
+ let normB = 0;
6380
+ for (let i = 0; i < a.length; i++) {
6381
+ dotProduct += a[i] * b[i];
6382
+ normA += a[i] * a[i];
6383
+ normB += b[i] * b[i];
4763
6384
  }
6385
+ const denominator = Math.sqrt(normA) * Math.sqrt(normB);
6386
+ return denominator === 0 ? 0 : dotProduct / denominator;
4764
6387
  }
4765
6388
  };
4766
6389
  }
@@ -4774,6 +6397,12 @@ var init_adapter = __esm({
4774
6397
  "use strict";
4775
6398
  MessagingAdapter = class extends EventEmitter {
4776
6399
  status = "disconnected";
6400
+ async sendPhoto(_chatId, _photo, _caption) {
6401
+ return void 0;
6402
+ }
6403
+ async sendFile(_chatId, _file, _fileName, _caption) {
6404
+ return void 0;
6405
+ }
4777
6406
  getStatus() {
4778
6407
  return this.status;
4779
6408
  }
@@ -4782,7 +6411,7 @@ var init_adapter = __esm({
4782
6411
  });
4783
6412
 
4784
6413
  // ../messaging/dist/adapters/telegram.js
4785
- import { Bot } from "grammy";
6414
+ import { Bot, InputFile } from "grammy";
4786
6415
  function mapParseMode(mode) {
4787
6416
  if (mode === "markdown")
4788
6417
  return "MarkdownV2";
@@ -4805,21 +6434,65 @@ var init_telegram = __esm({
4805
6434
  async connect() {
4806
6435
  this.status = "connecting";
4807
6436
  this.bot.on("message:text", (ctx) => {
6437
+ this.emit("message", this.normalizeMessage(ctx.message, ctx.message.text));
6438
+ });
6439
+ this.bot.on("message:photo", async (ctx) => {
4808
6440
  const msg = ctx.message;
4809
- const normalized = {
4810
- id: String(msg.message_id),
4811
- platform: "telegram",
4812
- chatId: String(msg.chat.id),
4813
- chatType: msg.chat.type === "private" ? "dm" : "group",
4814
- userId: String(msg.from.id),
4815
- userName: msg.from.username ?? String(msg.from.id),
4816
- displayName: [msg.from.first_name, msg.from.last_name].filter(Boolean).join(" "),
4817
- text: msg.text,
4818
- timestamp: new Date(msg.date * 1e3),
4819
- replyToMessageId: msg.reply_to_message ? String(msg.reply_to_message.message_id) : void 0
4820
- };
6441
+ const caption = msg.caption ?? "";
6442
+ const text = caption || "[Photo]";
6443
+ const photo = msg.photo[msg.photo.length - 1];
6444
+ const attachment = await this.downloadAttachment(photo.file_id, "image", "image/jpeg");
6445
+ const normalized = this.normalizeMessage(msg, text);
6446
+ normalized.attachments = attachment ? [attachment] : void 0;
6447
+ this.emit("message", normalized);
6448
+ });
6449
+ this.bot.on("message:voice", async (ctx) => {
6450
+ const msg = ctx.message;
6451
+ const attachment = await this.downloadAttachment(msg.voice.file_id, "audio", msg.voice.mime_type ?? "audio/ogg");
6452
+ const normalized = this.normalizeMessage(msg, "[Voice message]");
6453
+ normalized.attachments = attachment ? [attachment] : void 0;
6454
+ this.emit("message", normalized);
6455
+ });
6456
+ this.bot.on("message:audio", async (ctx) => {
6457
+ const msg = ctx.message;
6458
+ const caption = msg.caption ?? "";
6459
+ const text = caption || `[Audio: ${msg.audio.file_name ?? "audio"}]`;
6460
+ const attachment = await this.downloadAttachment(msg.audio.file_id, "audio", msg.audio.mime_type ?? "audio/mpeg");
6461
+ const normalized = this.normalizeMessage(msg, text);
6462
+ normalized.attachments = attachment ? [attachment] : void 0;
6463
+ this.emit("message", normalized);
6464
+ });
6465
+ this.bot.on("message:video", async (ctx) => {
6466
+ const msg = ctx.message;
6467
+ const caption = msg.caption ?? "";
6468
+ const text = caption || "[Video]";
6469
+ const attachment = await this.downloadAttachment(msg.video.file_id, "video", msg.video.mime_type ?? "video/mp4");
6470
+ const normalized = this.normalizeMessage(msg, text);
6471
+ normalized.attachments = attachment ? [attachment] : void 0;
6472
+ this.emit("message", normalized);
6473
+ });
6474
+ this.bot.on("message:document", async (ctx) => {
6475
+ const msg = ctx.message;
6476
+ const doc = msg.document;
6477
+ const caption = msg.caption ?? "";
6478
+ const text = caption || `[Document: ${doc.file_name ?? "file"}]`;
6479
+ const attachment = await this.downloadAttachment(doc.file_id, "document", doc.mime_type ?? "application/octet-stream", doc.file_name);
6480
+ const normalized = this.normalizeMessage(msg, text);
6481
+ normalized.attachments = attachment ? [attachment] : void 0;
6482
+ this.emit("message", normalized);
6483
+ });
6484
+ this.bot.on("message:video_note", async (ctx) => {
6485
+ const msg = ctx.message;
6486
+ const attachment = await this.downloadAttachment(msg.video_note.file_id, "video", "video/mp4");
6487
+ const normalized = this.normalizeMessage(msg, "[Video note]");
6488
+ normalized.attachments = attachment ? [attachment] : void 0;
4821
6489
  this.emit("message", normalized);
4822
6490
  });
6491
+ this.bot.on("message:sticker", (ctx) => {
6492
+ const msg = ctx.message;
6493
+ const emoji = msg.sticker.emoji ?? "\u{1F3F7}\uFE0F";
6494
+ this.emit("message", this.normalizeMessage(msg, `[Sticker: ${emoji}]`));
6495
+ });
4823
6496
  this.bot.catch((err) => {
4824
6497
  this.emit("error", err.error);
4825
6498
  });
@@ -4842,12 +6515,58 @@ var init_telegram = __esm({
4842
6515
  });
4843
6516
  return String(result.message_id);
4844
6517
  }
4845
- async editMessage(chatId, messageId, text) {
4846
- await this.bot.api.editMessageText(Number(chatId), Number(messageId), text);
6518
+ async editMessage(chatId, messageId, text, options) {
6519
+ await this.bot.api.editMessageText(Number(chatId), Number(messageId), text, {
6520
+ parse_mode: mapParseMode(options?.parseMode)
6521
+ });
4847
6522
  }
4848
6523
  async deleteMessage(chatId, messageId) {
4849
6524
  await this.bot.api.deleteMessage(Number(chatId), Number(messageId));
4850
6525
  }
6526
+ async sendPhoto(chatId, photo, caption) {
6527
+ const result = await this.bot.api.sendPhoto(Number(chatId), new InputFile(photo, "image.png"), { caption });
6528
+ return String(result.message_id);
6529
+ }
6530
+ async sendFile(chatId, file, fileName, caption) {
6531
+ const result = await this.bot.api.sendDocument(Number(chatId), new InputFile(file, fileName), { caption });
6532
+ return String(result.message_id);
6533
+ }
6534
+ normalizeMessage(msg, text) {
6535
+ return {
6536
+ id: String(msg.message_id),
6537
+ platform: "telegram",
6538
+ chatId: String(msg.chat.id),
6539
+ chatType: msg.chat.type === "private" ? "dm" : "group",
6540
+ userId: String(msg.from.id),
6541
+ userName: msg.from.username ?? String(msg.from.id),
6542
+ displayName: [msg.from.first_name, msg.from.last_name].filter(Boolean).join(" "),
6543
+ text,
6544
+ timestamp: new Date(msg.date * 1e3),
6545
+ replyToMessageId: msg.reply_to_message ? String(msg.reply_to_message.message_id) : void 0
6546
+ };
6547
+ }
6548
+ async downloadAttachment(fileId, type, mimeType, fileName) {
6549
+ try {
6550
+ const file = await this.bot.api.getFile(fileId);
6551
+ const filePath = file.file_path;
6552
+ if (!filePath)
6553
+ return void 0;
6554
+ const url = `https://api.telegram.org/file/bot${this.bot.token}/${filePath}`;
6555
+ const response = await fetch(url);
6556
+ if (!response.ok)
6557
+ return void 0;
6558
+ const buffer = Buffer.from(await response.arrayBuffer());
6559
+ return {
6560
+ type,
6561
+ mimeType,
6562
+ fileName: fileName ?? filePath.split("/").pop(),
6563
+ size: buffer.length,
6564
+ data: buffer
6565
+ };
6566
+ } catch {
6567
+ return void 0;
6568
+ }
6569
+ }
4851
6570
  };
4852
6571
  }
4853
6572
  });
@@ -4877,22 +6596,29 @@ var init_discord = __esm({
4877
6596
  GatewayIntentBits.DirectMessages
4878
6597
  ]
4879
6598
  });
4880
- this.client.on(Events.MessageCreate, (message) => {
6599
+ this.client.on(Events.MessageCreate, async (message) => {
4881
6600
  if (message.author.bot)
4882
6601
  return;
4883
- const normalized = {
4884
- id: message.id,
4885
- platform: "discord",
4886
- chatId: message.channelId,
4887
- chatType: message.channel.isDMBased() ? "dm" : "group",
4888
- userId: message.author.id,
4889
- userName: message.author.username,
4890
- displayName: message.author.displayName,
4891
- text: message.content,
4892
- timestamp: message.createdAt,
4893
- replyToMessageId: message.reference?.messageId ?? void 0
4894
- };
4895
- this.emit("message", normalized);
6602
+ try {
6603
+ const attachments = await this.downloadAttachments(message);
6604
+ const text = message.content || this.inferTextFromAttachments(attachments);
6605
+ const normalized = {
6606
+ id: message.id,
6607
+ platform: "discord",
6608
+ chatId: message.channelId,
6609
+ chatType: message.channel.isDMBased() ? "dm" : "group",
6610
+ userId: message.author.id,
6611
+ userName: message.author.username,
6612
+ displayName: message.author.displayName,
6613
+ text,
6614
+ timestamp: message.createdAt,
6615
+ replyToMessageId: message.reference?.messageId ?? void 0,
6616
+ attachments: attachments.length > 0 ? attachments : void 0
6617
+ };
6618
+ this.emit("message", normalized);
6619
+ } catch (err) {
6620
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
6621
+ }
4896
6622
  });
4897
6623
  this.client.on(Events.ClientReady, () => {
4898
6624
  this.status = "connected";
@@ -4924,7 +6650,7 @@ var init_discord = __esm({
4924
6650
  const message = await channel.send(text);
4925
6651
  return message.id;
4926
6652
  }
4927
- async editMessage(chatId, messageId, text) {
6653
+ async editMessage(chatId, messageId, text, _options) {
4928
6654
  if (!this.client)
4929
6655
  throw new Error("Client is not connected");
4930
6656
  const channel = await this.client.channels.fetch(chatId);
@@ -4944,6 +6670,82 @@ var init_discord = __esm({
4944
6670
  const message = await channel.messages.fetch(messageId);
4945
6671
  await message.delete();
4946
6672
  }
6673
+ async sendPhoto(chatId, photo, caption) {
6674
+ if (!this.client)
6675
+ return void 0;
6676
+ const channel = await this.client.channels.fetch(chatId);
6677
+ if (!channel?.isTextBased() || !("send" in channel))
6678
+ return void 0;
6679
+ const msg = await channel.send({
6680
+ content: caption,
6681
+ files: [{ attachment: photo, name: "image.png" }]
6682
+ });
6683
+ return msg.id;
6684
+ }
6685
+ async sendFile(chatId, file, fileName, caption) {
6686
+ if (!this.client)
6687
+ return void 0;
6688
+ const channel = await this.client.channels.fetch(chatId);
6689
+ if (!channel?.isTextBased() || !("send" in channel))
6690
+ return void 0;
6691
+ const msg = await channel.send({
6692
+ content: caption,
6693
+ files: [{ attachment: file, name: fileName }]
6694
+ });
6695
+ return msg.id;
6696
+ }
6697
+ // ── Private helpers ──────────────────────────────────────────────
6698
+ async downloadAttachments(message) {
6699
+ const result = [];
6700
+ const discordAttachments = message.attachments;
6701
+ if (!discordAttachments || discordAttachments.size === 0)
6702
+ return result;
6703
+ for (const [, att] of discordAttachments) {
6704
+ try {
6705
+ const res = await fetch(att.url);
6706
+ if (!res.ok)
6707
+ continue;
6708
+ const arrayBuffer = await res.arrayBuffer();
6709
+ const data = Buffer.from(arrayBuffer);
6710
+ const type = this.classifyContentType(att.contentType);
6711
+ result.push({
6712
+ type,
6713
+ url: att.url,
6714
+ mimeType: att.contentType ?? void 0,
6715
+ fileName: att.name ?? void 0,
6716
+ size: att.size ?? data.length,
6717
+ data
6718
+ });
6719
+ } catch {
6720
+ }
6721
+ }
6722
+ return result;
6723
+ }
6724
+ classifyContentType(contentType) {
6725
+ if (!contentType)
6726
+ return "other";
6727
+ if (contentType.startsWith("image/"))
6728
+ return "image";
6729
+ if (contentType.startsWith("audio/"))
6730
+ return "audio";
6731
+ if (contentType.startsWith("video/"))
6732
+ return "video";
6733
+ return "document";
6734
+ }
6735
+ inferTextFromAttachments(attachments) {
6736
+ if (attachments.length === 0)
6737
+ return "";
6738
+ const types = attachments.map((a) => a.type);
6739
+ if (types.includes("image"))
6740
+ return "[Photo]";
6741
+ if (types.includes("audio"))
6742
+ return "[Voice message]";
6743
+ if (types.includes("video"))
6744
+ return "[Video]";
6745
+ if (types.includes("document"))
6746
+ return "[Document]";
6747
+ return "[File]";
6748
+ }
4947
6749
  };
4948
6750
  }
4949
6751
  });
@@ -4962,7 +6764,7 @@ var init_matrix = __esm({
4962
6764
  botUserId;
4963
6765
  constructor(homeserverUrl, accessToken, botUserId) {
4964
6766
  super();
4965
- this.homeserverUrl = homeserverUrl;
6767
+ this.homeserverUrl = homeserverUrl.replace(/\/+$/, "");
4966
6768
  this.accessToken = accessToken;
4967
6769
  this.botUserId = botUserId;
4968
6770
  }
@@ -4972,23 +6774,20 @@ var init_matrix = __esm({
4972
6774
  const storageProvider = new SimpleFsStorageProvider("./data/matrix-storage");
4973
6775
  this.client = new MatrixClient(this.homeserverUrl, this.accessToken, storageProvider);
4974
6776
  AutojoinRoomsMixin.setupOnClient(this.client);
4975
- this.client.on("room.message", (roomId, event) => {
6777
+ this.client.on("room.message", async (roomId, event) => {
4976
6778
  if (event.sender === this.botUserId)
4977
6779
  return;
4978
- if (event.content?.msgtype !== "m.text")
6780
+ const msgtype = event.content?.msgtype;
6781
+ if (!msgtype)
4979
6782
  return;
4980
- const normalized = {
4981
- id: event.event_id,
4982
- platform: "matrix",
4983
- chatId: roomId,
4984
- chatType: "group",
4985
- userId: event.sender,
4986
- userName: event.sender.split(":")[0].slice(1),
4987
- text: event.content.body,
4988
- timestamp: new Date(event.origin_server_ts),
4989
- replyToMessageId: event.content["m.relates_to"]?.["m.in_reply_to"]?.event_id
4990
- };
4991
- this.emit("message", normalized);
6783
+ try {
6784
+ const message = await this.normalizeEvent(roomId, event, msgtype);
6785
+ if (message) {
6786
+ this.emit("message", message);
6787
+ }
6788
+ } catch (err) {
6789
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
6790
+ }
4992
6791
  });
4993
6792
  await this.client.start();
4994
6793
  this.status = "connected";
@@ -4999,27 +6798,177 @@ var init_matrix = __esm({
4999
6798
  this.status = "disconnected";
5000
6799
  this.emit("disconnected");
5001
6800
  }
5002
- async sendMessage(chatId, text, _options) {
6801
+ async sendMessage(chatId, text, options) {
6802
+ if (options?.parseMode === "html") {
6803
+ const eventId2 = await this.client.sendEvent(chatId, "m.room.message", {
6804
+ msgtype: "m.text",
6805
+ body: text.replace(/<[^>]*>/g, ""),
6806
+ format: "org.matrix.custom.html",
6807
+ formatted_body: text
6808
+ });
6809
+ return eventId2;
6810
+ }
5003
6811
  const eventId = await this.client.sendText(chatId, text);
5004
6812
  return eventId;
5005
6813
  }
5006
- async editMessage(chatId, messageId, text) {
5007
- await this.client.sendEvent(chatId, "m.room.message", {
6814
+ async editMessage(chatId, messageId, text, options) {
6815
+ const isHtml = options?.parseMode === "html";
6816
+ const content = {
5008
6817
  "msgtype": "m.text",
5009
- "body": "* " + text,
6818
+ "body": "* " + (isHtml ? text.replace(/<[^>]*>/g, "") : text),
5010
6819
  "m.new_content": {
5011
6820
  msgtype: "m.text",
5012
- body: text
6821
+ body: isHtml ? text.replace(/<[^>]*>/g, "") : text,
6822
+ ...isHtml ? { format: "org.matrix.custom.html", formatted_body: text } : {}
5013
6823
  },
5014
6824
  "m.relates_to": {
5015
6825
  rel_type: "m.replace",
5016
6826
  event_id: messageId
5017
6827
  }
5018
- });
6828
+ };
6829
+ await this.client.sendEvent(chatId, "m.room.message", content);
5019
6830
  }
5020
6831
  async deleteMessage(chatId, messageId) {
5021
6832
  await this.client.redactEvent(chatId, messageId);
5022
6833
  }
6834
+ async sendPhoto(chatId, photo, caption) {
6835
+ const mxcUrl = await this.client.uploadContent(photo, "image/png", "image.png");
6836
+ const content = {
6837
+ msgtype: "m.image",
6838
+ body: caption ?? "image.png",
6839
+ url: mxcUrl,
6840
+ info: {
6841
+ mimetype: "image/png",
6842
+ size: photo.length
6843
+ }
6844
+ };
6845
+ const eventId = await this.client.sendEvent(chatId, "m.room.message", content);
6846
+ return eventId;
6847
+ }
6848
+ async sendFile(chatId, file, fileName, caption) {
6849
+ const mimeType = this.guessMimeType(fileName);
6850
+ const mxcUrl = await this.client.uploadContent(file, mimeType, fileName);
6851
+ const content = {
6852
+ msgtype: "m.file",
6853
+ body: caption ?? fileName,
6854
+ filename: fileName,
6855
+ url: mxcUrl,
6856
+ info: {
6857
+ mimetype: mimeType,
6858
+ size: file.length
6859
+ }
6860
+ };
6861
+ const eventId = await this.client.sendEvent(chatId, "m.room.message", content);
6862
+ return eventId;
6863
+ }
6864
+ // ── Private helpers ──────────────────────────────────────────────
6865
+ async normalizeEvent(roomId, event, msgtype) {
6866
+ const base = {
6867
+ id: event.event_id,
6868
+ platform: "matrix",
6869
+ chatId: roomId,
6870
+ chatType: "group",
6871
+ userId: event.sender,
6872
+ userName: event.sender.split(":")[0].slice(1),
6873
+ timestamp: new Date(event.origin_server_ts),
6874
+ replyToMessageId: event.content["m.relates_to"]?.["m.in_reply_to"]?.event_id
6875
+ };
6876
+ switch (msgtype) {
6877
+ case "m.text":
6878
+ return { ...base, text: event.content.body };
6879
+ case "m.image": {
6880
+ const attachment = await this.downloadAttachment(event.content, "image");
6881
+ return {
6882
+ ...base,
6883
+ text: event.content.body ?? "[Photo]",
6884
+ attachments: attachment ? [attachment] : void 0
6885
+ };
6886
+ }
6887
+ case "m.audio": {
6888
+ const attachment = await this.downloadAttachment(event.content, "audio");
6889
+ return {
6890
+ ...base,
6891
+ text: event.content.body ?? "[Voice message]",
6892
+ attachments: attachment ? [attachment] : void 0
6893
+ };
6894
+ }
6895
+ case "m.video": {
6896
+ const attachment = await this.downloadAttachment(event.content, "video");
6897
+ return {
6898
+ ...base,
6899
+ text: event.content.body ?? "[Video]",
6900
+ attachments: attachment ? [attachment] : void 0
6901
+ };
6902
+ }
6903
+ case "m.file": {
6904
+ const attachment = await this.downloadAttachment(event.content, "document");
6905
+ return {
6906
+ ...base,
6907
+ text: event.content.body ?? "[Document]",
6908
+ attachments: attachment ? [attachment] : void 0
6909
+ };
6910
+ }
6911
+ default:
6912
+ if (event.content.body) {
6913
+ return { ...base, text: event.content.body };
6914
+ }
6915
+ return void 0;
6916
+ }
6917
+ }
6918
+ /**
6919
+ * Download a Matrix media file from an mxc:// URL.
6920
+ * Uses the /_matrix/media/v3/download endpoint.
6921
+ */
6922
+ async downloadAttachment(content, type) {
6923
+ const mxcUrl = content.url;
6924
+ if (!mxcUrl || !mxcUrl.startsWith("mxc://"))
6925
+ return void 0;
6926
+ const info = content.info ?? {};
6927
+ const mimeType = info.mimetype;
6928
+ const size = info.size;
6929
+ const fileName = content.filename ?? content.body ?? "file";
6930
+ try {
6931
+ const mxcParts = mxcUrl.slice(6);
6932
+ const downloadUrl = `${this.homeserverUrl}/_matrix/media/v3/download/${mxcParts}`;
6933
+ const res = await fetch(downloadUrl, {
6934
+ headers: { Authorization: `Bearer ${this.accessToken}` }
6935
+ });
6936
+ if (!res.ok)
6937
+ return void 0;
6938
+ const arrayBuffer = await res.arrayBuffer();
6939
+ const data = Buffer.from(arrayBuffer);
6940
+ return {
6941
+ type,
6942
+ mimeType,
6943
+ fileName,
6944
+ size: size ?? data.length,
6945
+ data
6946
+ };
6947
+ } catch {
6948
+ return void 0;
6949
+ }
6950
+ }
6951
+ guessMimeType(fileName) {
6952
+ const ext = fileName.split(".").pop()?.toLowerCase();
6953
+ const mimeMap = {
6954
+ pdf: "application/pdf",
6955
+ txt: "text/plain",
6956
+ json: "application/json",
6957
+ csv: "text/csv",
6958
+ png: "image/png",
6959
+ jpg: "image/jpeg",
6960
+ jpeg: "image/jpeg",
6961
+ gif: "image/gif",
6962
+ mp3: "audio/mpeg",
6963
+ ogg: "audio/ogg",
6964
+ mp4: "video/mp4",
6965
+ zip: "application/zip",
6966
+ doc: "application/msword",
6967
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
6968
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
6969
+ };
6970
+ return mimeMap[ext ?? ""] ?? "application/octet-stream";
6971
+ }
5023
6972
  };
5024
6973
  }
5025
6974
  });
@@ -5033,6 +6982,7 @@ var init_whatsapp = __esm({
5033
6982
  WhatsAppAdapter = class extends MessagingAdapter {
5034
6983
  platform = "whatsapp";
5035
6984
  socket;
6985
+ downloadMedia;
5036
6986
  dataPath;
5037
6987
  constructor(dataPath) {
5038
6988
  super();
@@ -5041,7 +6991,9 @@ var init_whatsapp = __esm({
5041
6991
  async connect() {
5042
6992
  this.status = "connecting";
5043
6993
  const baileys = await import("@whiskeysockets/baileys");
5044
- const { makeWASocket, useMultiFileAuthState, DisconnectReason } = baileys.default ?? baileys;
6994
+ const mod = baileys.default ?? baileys;
6995
+ const { makeWASocket, useMultiFileAuthState, DisconnectReason, downloadMediaMessage } = mod;
6996
+ this.downloadMedia = downloadMediaMessage;
5045
6997
  const { state, saveCreds } = await useMultiFileAuthState(this.dataPath);
5046
6998
  this.socket = makeWASocket({
5047
6999
  auth: state,
@@ -5071,21 +7023,9 @@ var init_whatsapp = __esm({
5071
7023
  continue;
5072
7024
  if (message.key.fromMe)
5073
7025
  continue;
5074
- const text = message.message.conversation ?? message.message.extendedTextMessage?.text;
5075
- if (!text)
5076
- continue;
5077
- const normalized = {
5078
- id: message.key.id ?? "",
5079
- platform: "whatsapp",
5080
- chatId: message.key.remoteJid ?? "",
5081
- chatType: message.key.remoteJid?.endsWith("@g.us") ? "group" : "dm",
5082
- userId: message.key.participant ?? message.key.remoteJid ?? "",
5083
- userName: message.pushName ?? message.key.participant ?? message.key.remoteJid ?? "",
5084
- text,
5085
- timestamp: new Date(message.messageTimestamp * 1e3),
5086
- replyToMessageId: message.message.extendedTextMessage?.contextInfo?.stanzaId ?? void 0
5087
- };
5088
- this.emit("message", normalized);
7026
+ this.processMessage(message).catch((err) => {
7027
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
7028
+ });
5089
7029
  }
5090
7030
  });
5091
7031
  }
@@ -5104,7 +7044,7 @@ var init_whatsapp = __esm({
5104
7044
  } : void 0);
5105
7045
  return msg?.key?.id ?? "";
5106
7046
  }
5107
- async editMessage(chatId, messageId, text) {
7047
+ async editMessage(chatId, messageId, text, _options) {
5108
7048
  await this.socket.sendMessage(chatId, {
5109
7049
  text,
5110
7050
  edit: {
@@ -5123,6 +7063,128 @@ var init_whatsapp = __esm({
5123
7063
  }
5124
7064
  });
5125
7065
  }
7066
+ async sendPhoto(chatId, photo, caption) {
7067
+ const msg = await this.socket.sendMessage(chatId, {
7068
+ image: photo,
7069
+ caption
7070
+ });
7071
+ return msg?.key?.id;
7072
+ }
7073
+ async sendFile(chatId, file, fileName, caption) {
7074
+ const msg = await this.socket.sendMessage(chatId, {
7075
+ document: file,
7076
+ fileName,
7077
+ caption,
7078
+ mimetype: this.guessMimeType(fileName)
7079
+ });
7080
+ return msg?.key?.id;
7081
+ }
7082
+ // ── Private helpers ──────────────────────────────────────────────
7083
+ async processMessage(message) {
7084
+ const msg = message.message;
7085
+ const text = msg.conversation ?? msg.extendedTextMessage?.text ?? msg.imageMessage?.caption ?? msg.videoMessage?.caption ?? msg.documentMessage?.caption ?? "";
7086
+ const attachments = [];
7087
+ let fallbackText = text;
7088
+ if (msg.imageMessage) {
7089
+ const data = await this.downloadMediaSafe(message);
7090
+ if (data) {
7091
+ attachments.push({
7092
+ type: "image",
7093
+ mimeType: msg.imageMessage.mimetype ?? "image/jpeg",
7094
+ size: msg.imageMessage.fileLength ?? data.length,
7095
+ data
7096
+ });
7097
+ }
7098
+ if (!fallbackText)
7099
+ fallbackText = "[Photo]";
7100
+ } else if (msg.audioMessage) {
7101
+ const data = await this.downloadMediaSafe(message);
7102
+ if (data) {
7103
+ attachments.push({
7104
+ type: "audio",
7105
+ mimeType: msg.audioMessage.mimetype ?? "audio/ogg",
7106
+ size: msg.audioMessage.fileLength ?? data.length,
7107
+ data
7108
+ });
7109
+ }
7110
+ if (!fallbackText)
7111
+ fallbackText = "[Voice message]";
7112
+ } else if (msg.videoMessage) {
7113
+ const data = await this.downloadMediaSafe(message);
7114
+ if (data) {
7115
+ attachments.push({
7116
+ type: "video",
7117
+ mimeType: msg.videoMessage.mimetype ?? "video/mp4",
7118
+ size: msg.videoMessage.fileLength ?? data.length,
7119
+ data
7120
+ });
7121
+ }
7122
+ if (!fallbackText)
7123
+ fallbackText = "[Video]";
7124
+ } else if (msg.documentMessage) {
7125
+ const data = await this.downloadMediaSafe(message);
7126
+ if (data) {
7127
+ attachments.push({
7128
+ type: "document",
7129
+ mimeType: msg.documentMessage.mimetype ?? "application/octet-stream",
7130
+ fileName: msg.documentMessage.fileName ?? "document",
7131
+ size: msg.documentMessage.fileLength ?? data.length,
7132
+ data
7133
+ });
7134
+ }
7135
+ if (!fallbackText)
7136
+ fallbackText = "[Document]";
7137
+ } else if (msg.stickerMessage) {
7138
+ if (!text)
7139
+ return;
7140
+ }
7141
+ if (!fallbackText && attachments.length === 0)
7142
+ return;
7143
+ const normalized = {
7144
+ id: message.key.id ?? "",
7145
+ platform: "whatsapp",
7146
+ chatId: message.key.remoteJid ?? "",
7147
+ chatType: message.key.remoteJid?.endsWith("@g.us") ? "group" : "dm",
7148
+ userId: message.key.participant ?? message.key.remoteJid ?? "",
7149
+ userName: message.pushName ?? message.key.participant ?? message.key.remoteJid ?? "",
7150
+ text: fallbackText,
7151
+ timestamp: new Date(message.messageTimestamp * 1e3),
7152
+ replyToMessageId: msg.extendedTextMessage?.contextInfo?.stanzaId ?? void 0,
7153
+ attachments: attachments.length > 0 ? attachments : void 0
7154
+ };
7155
+ this.emit("message", normalized);
7156
+ }
7157
+ async downloadMediaSafe(message) {
7158
+ try {
7159
+ if (!this.downloadMedia)
7160
+ return void 0;
7161
+ const buffer = await this.downloadMedia(message, "buffer", {});
7162
+ return Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
7163
+ } catch {
7164
+ return void 0;
7165
+ }
7166
+ }
7167
+ guessMimeType(fileName) {
7168
+ const ext = fileName.split(".").pop()?.toLowerCase();
7169
+ const mimeMap = {
7170
+ pdf: "application/pdf",
7171
+ txt: "text/plain",
7172
+ json: "application/json",
7173
+ csv: "text/csv",
7174
+ png: "image/png",
7175
+ jpg: "image/jpeg",
7176
+ jpeg: "image/jpeg",
7177
+ gif: "image/gif",
7178
+ mp3: "audio/mpeg",
7179
+ ogg: "audio/ogg",
7180
+ mp4: "video/mp4",
7181
+ zip: "application/zip",
7182
+ doc: "application/msword",
7183
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
7184
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
7185
+ };
7186
+ return mimeMap[ext ?? ""] ?? "application/octet-stream";
7187
+ }
5126
7188
  };
5127
7189
  }
5128
7190
  });
@@ -5192,7 +7254,7 @@ var init_signal = __esm({
5192
7254
  const result = await res.json();
5193
7255
  return String(result.timestamp ?? Date.now());
5194
7256
  }
5195
- async editMessage(_chatId, _messageId, _text) {
7257
+ async editMessage(_chatId, _messageId, _text, _options) {
5196
7258
  throw new Error("Signal does not support message editing");
5197
7259
  }
5198
7260
  async deleteMessage(chatId, messageId) {
@@ -5217,10 +7279,24 @@ var init_signal = __esm({
5217
7279
  const messages = await res.json();
5218
7280
  for (const envelope of messages) {
5219
7281
  const dataMessage = envelope.envelope?.dataMessage;
5220
- if (!dataMessage?.message)
7282
+ if (!dataMessage)
7283
+ continue;
7284
+ if (!dataMessage.message && (!dataMessage.attachments || dataMessage.attachments.length === 0))
5221
7285
  continue;
5222
7286
  const data = envelope.envelope;
5223
7287
  const chatId = dataMessage.groupInfo?.groupId ? `group.${dataMessage.groupInfo.groupId}` : data.sourceNumber ?? data.source ?? "";
7288
+ const attachments = [];
7289
+ if (dataMessage.attachments) {
7290
+ for (const att of dataMessage.attachments) {
7291
+ const downloaded = await this.downloadAttachment(att);
7292
+ if (downloaded) {
7293
+ attachments.push(downloaded);
7294
+ }
7295
+ }
7296
+ }
7297
+ const text = dataMessage.message || this.inferTextFromAttachments(attachments) || "";
7298
+ if (!text && attachments.length === 0)
7299
+ continue;
5224
7300
  const normalized = {
5225
7301
  id: String(dataMessage.timestamp ?? Date.now()),
5226
7302
  platform: "signal",
@@ -5229,12 +7305,59 @@ var init_signal = __esm({
5229
7305
  userId: data.sourceNumber ?? data.source ?? "",
5230
7306
  userName: data.sourceName ?? data.sourceNumber ?? data.source ?? "",
5231
7307
  displayName: data.sourceName,
5232
- text: dataMessage.message,
5233
- timestamp: new Date(dataMessage.timestamp ?? Date.now())
7308
+ text,
7309
+ timestamp: new Date(dataMessage.timestamp ?? Date.now()),
7310
+ attachments: attachments.length > 0 ? attachments : void 0
5234
7311
  };
5235
7312
  this.emit("message", normalized);
5236
7313
  }
5237
7314
  }
7315
+ async downloadAttachment(att) {
7316
+ if (!att.id)
7317
+ return void 0;
7318
+ try {
7319
+ const res = await fetch(`${this.apiUrl}/v1/attachments/${att.id}`);
7320
+ if (!res.ok)
7321
+ return void 0;
7322
+ const arrayBuffer = await res.arrayBuffer();
7323
+ const data = Buffer.from(arrayBuffer);
7324
+ const type = this.classifyContentType(att.contentType);
7325
+ return {
7326
+ type,
7327
+ mimeType: att.contentType ?? void 0,
7328
+ fileName: att.filename ?? void 0,
7329
+ size: att.size ?? data.length,
7330
+ data
7331
+ };
7332
+ } catch {
7333
+ return void 0;
7334
+ }
7335
+ }
7336
+ classifyContentType(contentType) {
7337
+ if (!contentType)
7338
+ return "other";
7339
+ if (contentType.startsWith("image/"))
7340
+ return "image";
7341
+ if (contentType.startsWith("audio/"))
7342
+ return "audio";
7343
+ if (contentType.startsWith("video/"))
7344
+ return "video";
7345
+ return "document";
7346
+ }
7347
+ inferTextFromAttachments(attachments) {
7348
+ if (attachments.length === 0)
7349
+ return "";
7350
+ const types = attachments.map((a) => a.type);
7351
+ if (types.includes("image"))
7352
+ return "[Photo]";
7353
+ if (types.includes("audio"))
7354
+ return "[Voice message]";
7355
+ if (types.includes("video"))
7356
+ return "[Video]";
7357
+ if (types.includes("document"))
7358
+ return "[Document]";
7359
+ return "[File]";
7360
+ }
5238
7361
  };
5239
7362
  }
5240
7363
  });
@@ -5262,8 +7385,8 @@ var init_dist7 = __esm({
5262
7385
  });
5263
7386
 
5264
7387
  // ../core/dist/alfred.js
5265
- import fs5 from "node:fs";
5266
- import path7 from "node:path";
7388
+ import fs6 from "node:fs";
7389
+ import path8 from "node:path";
5267
7390
  import yaml2 from "js-yaml";
5268
7391
  var Alfred;
5269
7392
  var init_alfred = __esm({
@@ -5277,6 +7400,9 @@ var init_alfred = __esm({
5277
7400
  init_conversation_manager();
5278
7401
  init_message_pipeline();
5279
7402
  init_reminder_scheduler();
7403
+ init_speech_transcriber();
7404
+ init_response_formatter();
7405
+ init_embedding_service();
5280
7406
  Alfred = class {
5281
7407
  config;
5282
7408
  logger;
@@ -5284,6 +7410,9 @@ var init_alfred = __esm({
5284
7410
  pipeline;
5285
7411
  reminderScheduler;
5286
7412
  adapters = /* @__PURE__ */ new Map();
7413
+ formatter = new ResponseFormatter();
7414
+ calendarSkill;
7415
+ // CalendarSkill instance for today's events
5287
7416
  constructor(config) {
5288
7417
  this.config = config;
5289
7418
  this.logger = createLogger("alfred", config.logger.level);
@@ -5298,6 +7427,7 @@ var init_alfred = __esm({
5298
7427
  const memoryRepo = new MemoryRepository(db);
5299
7428
  const reminderRepo = new ReminderRepository(db);
5300
7429
  const noteRepo = new NoteRepository(db);
7430
+ const embeddingRepo = new EmbeddingRepository(db);
5301
7431
  this.logger.info("Storage initialized");
5302
7432
  const ruleEngine = new RuleEngine();
5303
7433
  const rules = this.loadSecurityRules();
@@ -5307,6 +7437,7 @@ var init_alfred = __esm({
5307
7437
  const llmProvider = createLLMProvider(this.config.llm);
5308
7438
  await llmProvider.initialize();
5309
7439
  this.logger.info({ provider: this.config.llm.provider, model: this.config.llm.model }, "LLM provider initialized");
7440
+ const embeddingService = new EmbeddingService(llmProvider, embeddingRepo, this.logger.child({ component: "embeddings" }));
5310
7441
  const skillSandbox = new SkillSandbox(this.logger.child({ component: "sandbox" }));
5311
7442
  const skillRegistry = new SkillRegistry();
5312
7443
  skillRegistry.register(new CalculatorSkill());
@@ -5320,7 +7451,7 @@ var init_alfred = __esm({
5320
7451
  skillRegistry.register(new NoteSkill(noteRepo));
5321
7452
  skillRegistry.register(new WeatherSkill());
5322
7453
  skillRegistry.register(new ShellSkill());
5323
- skillRegistry.register(new MemorySkill(memoryRepo));
7454
+ skillRegistry.register(new MemorySkill(memoryRepo, embeddingService));
5324
7455
  skillRegistry.register(new DelegateSkill(llmProvider, skillRegistry, skillSandbox, securityManager));
5325
7456
  skillRegistry.register(new EmailSkill(this.config.email ? {
5326
7457
  imap: this.config.email.imap,
@@ -5332,9 +7463,40 @@ var init_alfred = __esm({
5332
7463
  skillRegistry.register(new ClipboardSkill());
5333
7464
  skillRegistry.register(new ScreenshotSkill());
5334
7465
  skillRegistry.register(new BrowserSkill());
7466
+ skillRegistry.register(new ProfileSkill(userRepo));
7467
+ let calendarSkill;
7468
+ if (this.config.calendar) {
7469
+ try {
7470
+ const calendarProvider = await createCalendarProvider(this.config.calendar);
7471
+ calendarSkill = new CalendarSkill(calendarProvider);
7472
+ skillRegistry.register(calendarSkill);
7473
+ this.logger.info({ provider: this.config.calendar.provider }, "Calendar initialized");
7474
+ } catch (err) {
7475
+ this.logger.warn({ err }, "Calendar initialization failed, continuing without calendar");
7476
+ }
7477
+ }
7478
+ this.calendarSkill = calendarSkill;
5335
7479
  this.logger.info({ skills: skillRegistry.getAll().map((s) => s.metadata.name) }, "Skills registered");
7480
+ let speechTranscriber;
7481
+ if (this.config.speech?.apiKey) {
7482
+ speechTranscriber = new SpeechTranscriber(this.config.speech, this.logger.child({ component: "speech" }));
7483
+ this.logger.info({ provider: this.config.speech.provider }, "Speech-to-text initialized");
7484
+ }
5336
7485
  const conversationManager = new ConversationManager(conversationRepo);
5337
- this.pipeline = new MessagePipeline(llmProvider, conversationManager, userRepo, this.logger.child({ component: "pipeline" }), skillRegistry, skillSandbox, securityManager, memoryRepo);
7486
+ const inboxPath = path8.resolve(path8.dirname(this.config.storage.path), "inbox");
7487
+ this.pipeline = new MessagePipeline({
7488
+ llm: llmProvider,
7489
+ conversationManager,
7490
+ users: userRepo,
7491
+ logger: this.logger.child({ component: "pipeline" }),
7492
+ skillRegistry,
7493
+ skillSandbox,
7494
+ securityManager,
7495
+ memoryRepo,
7496
+ speechTranscriber,
7497
+ inboxPath,
7498
+ embeddingService
7499
+ });
5338
7500
  this.reminderScheduler = new ReminderScheduler(reminderRepo, async (platform, chatId, text) => {
5339
7501
  const adapter = this.adapters.get(platform);
5340
7502
  if (adapter) {
@@ -5420,14 +7582,16 @@ var init_alfred = __esm({
5420
7582
  }
5421
7583
  };
5422
7584
  const response = await this.pipeline.process(message, onProgress);
7585
+ const formatted = this.formatter.format(response, message.platform);
7586
+ const sendOpts = formatted.parseMode !== "text" ? { parseMode: formatted.parseMode } : void 0;
5423
7587
  if (statusMessageId) {
5424
7588
  try {
5425
- await adapter.editMessage(message.chatId, statusMessageId, response);
7589
+ await adapter.editMessage(message.chatId, statusMessageId, formatted.text, sendOpts);
5426
7590
  } catch {
5427
- await adapter.sendMessage(message.chatId, response);
7591
+ await adapter.sendMessage(message.chatId, formatted.text, sendOpts);
5428
7592
  }
5429
7593
  } else {
5430
- await adapter.sendMessage(message.chatId, response);
7594
+ await adapter.sendMessage(message.chatId, formatted.text, sendOpts);
5431
7595
  }
5432
7596
  } catch (error) {
5433
7597
  this.logger.error({ platform, err: error, chatId: message.chatId }, "Failed to handle message");
@@ -5449,22 +7613,22 @@ var init_alfred = __esm({
5449
7613
  });
5450
7614
  }
5451
7615
  loadSecurityRules() {
5452
- const rulesPath = path7.resolve(this.config.security.rulesPath);
7616
+ const rulesPath = path8.resolve(this.config.security.rulesPath);
5453
7617
  const rules = [];
5454
- if (!fs5.existsSync(rulesPath)) {
7618
+ if (!fs6.existsSync(rulesPath)) {
5455
7619
  this.logger.warn({ rulesPath }, "Security rules directory not found, using default deny");
5456
7620
  return rules;
5457
7621
  }
5458
- const stat = fs5.statSync(rulesPath);
7622
+ const stat = fs6.statSync(rulesPath);
5459
7623
  if (!stat.isDirectory()) {
5460
7624
  this.logger.warn({ rulesPath }, "Security rules path is not a directory");
5461
7625
  return rules;
5462
7626
  }
5463
- const files = fs5.readdirSync(rulesPath).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
7627
+ const files = fs6.readdirSync(rulesPath).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
5464
7628
  for (const file of files) {
5465
7629
  try {
5466
- const filePath = path7.join(rulesPath, file);
5467
- const content = fs5.readFileSync(filePath, "utf-8");
7630
+ const filePath = path8.join(rulesPath, file);
7631
+ const content = fs6.readFileSync(filePath, "utf-8");
5468
7632
  const parsed = yaml2.load(content);
5469
7633
  if (parsed?.rules && Array.isArray(parsed.rules)) {
5470
7634
  rules.push(...parsed.rules);
@@ -5488,6 +7652,9 @@ var init_dist8 = __esm({
5488
7652
  init_message_pipeline();
5489
7653
  init_conversation_manager();
5490
7654
  init_reminder_scheduler();
7655
+ init_speech_transcriber();
7656
+ init_response_formatter();
7657
+ init_embedding_service();
5491
7658
  }
5492
7659
  });
5493
7660
 
@@ -5558,8 +7725,8 @@ __export(setup_exports, {
5558
7725
  });
5559
7726
  import { createInterface } from "node:readline/promises";
5560
7727
  import { stdin as input, stdout as output } from "node:process";
5561
- import fs6 from "node:fs";
5562
- import path8 from "node:path";
7728
+ import fs7 from "node:fs";
7729
+ import path9 from "node:path";
5563
7730
  import yaml3 from "js-yaml";
5564
7731
  function green(s) {
5565
7732
  return `${GREEN}${s}${RESET}`;
@@ -5590,20 +7757,20 @@ function loadExistingConfig(projectRoot) {
5590
7757
  let shellEnabled = false;
5591
7758
  let writeInGroups = false;
5592
7759
  let rateLimit = 30;
5593
- const configPath = path8.join(projectRoot, "config", "default.yml");
5594
- if (fs6.existsSync(configPath)) {
7760
+ const configPath = path9.join(projectRoot, "config", "default.yml");
7761
+ if (fs7.existsSync(configPath)) {
5595
7762
  try {
5596
- const parsed = yaml3.load(fs6.readFileSync(configPath, "utf-8"));
7763
+ const parsed = yaml3.load(fs7.readFileSync(configPath, "utf-8"));
5597
7764
  if (parsed && typeof parsed === "object") {
5598
7765
  Object.assign(config, parsed);
5599
7766
  }
5600
7767
  } catch {
5601
7768
  }
5602
7769
  }
5603
- const envPath = path8.join(projectRoot, ".env");
5604
- if (fs6.existsSync(envPath)) {
7770
+ const envPath = path9.join(projectRoot, ".env");
7771
+ if (fs7.existsSync(envPath)) {
5605
7772
  try {
5606
- const lines = fs6.readFileSync(envPath, "utf-8").split("\n");
7773
+ const lines = fs7.readFileSync(envPath, "utf-8").split("\n");
5607
7774
  for (const line of lines) {
5608
7775
  const trimmed = line.trim();
5609
7776
  if (!trimmed || trimmed.startsWith("#"))
@@ -5616,10 +7783,10 @@ function loadExistingConfig(projectRoot) {
5616
7783
  } catch {
5617
7784
  }
5618
7785
  }
5619
- const rulesPath = path8.join(projectRoot, "config", "rules", "default-rules.yml");
5620
- if (fs6.existsSync(rulesPath)) {
7786
+ const rulesPath = path9.join(projectRoot, "config", "rules", "default-rules.yml");
7787
+ if (fs7.existsSync(rulesPath)) {
5621
7788
  try {
5622
- const rulesContent = yaml3.load(fs6.readFileSync(rulesPath, "utf-8"));
7789
+ const rulesContent = yaml3.load(fs7.readFileSync(rulesPath, "utf-8"));
5623
7790
  if (rulesContent?.rules) {
5624
7791
  shellEnabled = rulesContent.rules.some((r) => r.id === "allow-owner-admin" && r.effect === "allow");
5625
7792
  const writeDmRule = rulesContent.rules.find((r) => r.id === "allow-write-for-dm" || r.id === "allow-write-all");
@@ -5875,6 +8042,54 @@ ${bold("Email access (read & send emails via IMAP/SMTP)?")}`);
5875
8042
  } else {
5876
8043
  console.log(` ${dim("Email disabled \u2014 you can configure it later.")}`);
5877
8044
  }
8045
+ const speechProviders = ["openai", "groq"];
8046
+ const existingSpeechProvider = existing.config.speech?.provider ?? existing.env["ALFRED_SPEECH_PROVIDER"] ?? "";
8047
+ const existingSpeechIdx = speechProviders.indexOf(existingSpeechProvider);
8048
+ const defaultSpeechChoice = existingSpeechIdx >= 0 ? existingSpeechIdx + 1 : 0;
8049
+ console.log(`
8050
+ ${bold("Voice message transcription (Speech-to-Text via Whisper)?")}`);
8051
+ console.log(`${dim("Transcribes voice messages from Telegram, Discord, etc.")}`);
8052
+ const speechLabels = [
8053
+ "OpenAI Whisper \u2014 best quality",
8054
+ "Groq Whisper \u2014 fast & free"
8055
+ ];
8056
+ console.log(` ${cyan("0)")} None (disable voice transcription)${existingSpeechIdx === -1 ? ` ${dim("(current)")}` : ""}`);
8057
+ for (let i = 0; i < speechLabels.length; i++) {
8058
+ const cur = existingSpeechIdx === i ? ` ${dim("(current)")}` : "";
8059
+ console.log(` ${cyan(String(i + 1) + ")")} ${speechLabels[i]}${cur}`);
8060
+ }
8061
+ const speechChoice = await askNumber(rl, "> ", 0, speechProviders.length, defaultSpeechChoice);
8062
+ let speechProvider;
8063
+ let speechApiKey = "";
8064
+ let speechBaseUrl = "";
8065
+ if (speechChoice >= 1 && speechChoice <= speechProviders.length) {
8066
+ speechProvider = speechProviders[speechChoice - 1];
8067
+ }
8068
+ if (speechProvider === "openai") {
8069
+ const existingKey = existing.env["ALFRED_SPEECH_API_KEY"] ?? "";
8070
+ if (existingKey) {
8071
+ speechApiKey = await askWithDefault(rl, " OpenAI API key (for Whisper)", existingKey);
8072
+ } else {
8073
+ console.log(` ${dim("Uses your OpenAI API key for Whisper transcription.")}`);
8074
+ speechApiKey = await askRequired(rl, " OpenAI API key");
8075
+ }
8076
+ console.log(` ${green(">")} OpenAI Whisper: ${dim(maskKey(speechApiKey))}`);
8077
+ } else if (speechProvider === "groq") {
8078
+ const existingKey = existing.env["ALFRED_SPEECH_API_KEY"] ?? "";
8079
+ if (existingKey) {
8080
+ speechApiKey = await askWithDefault(rl, " Groq API key", existingKey);
8081
+ } else {
8082
+ console.log(` ${dim("Get your free API key at: https://console.groq.com/")}`);
8083
+ speechApiKey = await askRequired(rl, " Groq API key");
8084
+ }
8085
+ const existingUrl = existing.env["ALFRED_SPEECH_BASE_URL"] ?? "";
8086
+ if (existingUrl) {
8087
+ speechBaseUrl = await askWithDefault(rl, " Groq API URL", existingUrl);
8088
+ }
8089
+ console.log(` ${green(">")} Groq Whisper: ${dim(maskKey(speechApiKey))}`);
8090
+ } else {
8091
+ console.log(` ${dim("Voice transcription disabled \u2014 you can configure it later.")}`);
8092
+ }
5878
8093
  console.log(`
5879
8094
  ${bold("Security configuration:")}`);
5880
8095
  const existingOwnerId = existing.config.security?.ownerUserId ?? existing.env["ALFRED_OWNER_USER_ID"] ?? "";
@@ -5970,6 +8185,17 @@ ${bold("Writing configuration files...")}`);
5970
8185
  envLines.push("# ALFRED_EMAIL_USER=");
5971
8186
  envLines.push("# ALFRED_EMAIL_PASS=");
5972
8187
  }
8188
+ envLines.push("", "# === Speech-to-Text ===", "");
8189
+ if (speechProvider) {
8190
+ envLines.push(`ALFRED_SPEECH_PROVIDER=${speechProvider}`);
8191
+ envLines.push(`ALFRED_SPEECH_API_KEY=${speechApiKey}`);
8192
+ if (speechBaseUrl) {
8193
+ envLines.push(`ALFRED_SPEECH_BASE_URL=${speechBaseUrl}`);
8194
+ }
8195
+ } else {
8196
+ envLines.push("# ALFRED_SPEECH_PROVIDER=groq");
8197
+ envLines.push("# ALFRED_SPEECH_API_KEY=");
8198
+ }
5973
8199
  envLines.push("", "# === Security ===", "");
5974
8200
  if (ownerUserId) {
5975
8201
  envLines.push(`ALFRED_OWNER_USER_ID=${ownerUserId}`);
@@ -5977,12 +8203,12 @@ ${bold("Writing configuration files...")}`);
5977
8203
  envLines.push("# ALFRED_OWNER_USER_ID=");
5978
8204
  }
5979
8205
  envLines.push("");
5980
- const envPath = path8.join(projectRoot, ".env");
5981
- fs6.writeFileSync(envPath, envLines.join("\n"), "utf-8");
8206
+ const envPath = path9.join(projectRoot, ".env");
8207
+ fs7.writeFileSync(envPath, envLines.join("\n"), "utf-8");
5982
8208
  console.log(` ${green("+")} ${dim(".env")} written`);
5983
- const configDir = path8.join(projectRoot, "config");
5984
- if (!fs6.existsSync(configDir)) {
5985
- fs6.mkdirSync(configDir, { recursive: true });
8209
+ const configDir = path9.join(projectRoot, "config");
8210
+ if (!fs7.existsSync(configDir)) {
8211
+ fs7.mkdirSync(configDir, { recursive: true });
5986
8212
  }
5987
8213
  const config = {
5988
8214
  name: botName,
@@ -6030,6 +8256,13 @@ ${bold("Writing configuration files...")}`);
6030
8256
  auth: { user: emailUser, pass: emailPass }
6031
8257
  }
6032
8258
  } : {},
8259
+ ...speechProvider ? {
8260
+ speech: {
8261
+ provider: speechProvider,
8262
+ apiKey: speechApiKey,
8263
+ ...speechBaseUrl ? { baseUrl: speechBaseUrl } : {}
8264
+ }
8265
+ } : {},
6033
8266
  storage: {
6034
8267
  path: "./data/alfred.db"
6035
8268
  },
@@ -6047,12 +8280,12 @@ ${bold("Writing configuration files...")}`);
6047
8280
  config.security.ownerUserId = ownerUserId;
6048
8281
  }
6049
8282
  const yamlStr = "# Alfred \u2014 Configuration\n# Generated by `alfred setup`\n# Edit manually or re-run `alfred setup` to reconfigure.\n\n" + yaml3.dump(config, { lineWidth: 120, noRefs: true, sortKeys: false });
6050
- const configPath = path8.join(configDir, "default.yml");
6051
- fs6.writeFileSync(configPath, yamlStr, "utf-8");
8283
+ const configPath = path9.join(configDir, "default.yml");
8284
+ fs7.writeFileSync(configPath, yamlStr, "utf-8");
6052
8285
  console.log(` ${green("+")} ${dim("config/default.yml")} written`);
6053
- const rulesDir = path8.join(configDir, "rules");
6054
- if (!fs6.existsSync(rulesDir)) {
6055
- fs6.mkdirSync(rulesDir, { recursive: true });
8286
+ const rulesDir = path9.join(configDir, "rules");
8287
+ if (!fs7.existsSync(rulesDir)) {
8288
+ fs7.mkdirSync(rulesDir, { recursive: true });
6056
8289
  }
6057
8290
  const ownerAdminRule = enableShell && ownerUserId ? `
6058
8291
  # Allow admin actions (shell, etc.) for the owner only
@@ -6133,12 +8366,12 @@ ${ownerAdminRule}
6133
8366
  actions: ["*"]
6134
8367
  riskLevels: [read, write, destructive, admin]
6135
8368
  `;
6136
- const rulesPath = path8.join(rulesDir, "default-rules.yml");
6137
- fs6.writeFileSync(rulesPath, rulesYaml, "utf-8");
8369
+ const rulesPath = path9.join(rulesDir, "default-rules.yml");
8370
+ fs7.writeFileSync(rulesPath, rulesYaml, "utf-8");
6138
8371
  console.log(` ${green("+")} ${dim("config/rules/default-rules.yml")} written`);
6139
- const dataDir = path8.join(projectRoot, "data");
6140
- if (!fs6.existsSync(dataDir)) {
6141
- fs6.mkdirSync(dataDir, { recursive: true });
8372
+ const dataDir = path9.join(projectRoot, "data");
8373
+ if (!fs7.existsSync(dataDir)) {
8374
+ fs7.mkdirSync(dataDir, { recursive: true });
6142
8375
  console.log(` ${green("+")} ${dim("data/")} directory created`);
6143
8376
  }
6144
8377
  console.log("");
@@ -6172,6 +8405,15 @@ ${ownerAdminRule}
6172
8405
  } else {
6173
8406
  console.log(` ${bold("Email:")} ${dim("disabled")}`);
6174
8407
  }
8408
+ if (speechProvider) {
8409
+ const speechLabelMap = {
8410
+ openai: "OpenAI Whisper",
8411
+ groq: "Groq Whisper"
8412
+ };
8413
+ console.log(` ${bold("Voice:")} ${speechLabelMap[speechProvider]}`);
8414
+ } else {
8415
+ console.log(` ${bold("Voice:")} ${dim("disabled")}`);
8416
+ }
6175
8417
  if (ownerUserId) {
6176
8418
  console.log(` ${bold("Owner ID:")} ${ownerUserId}`);
6177
8419
  console.log(` ${bold("Shell access:")} ${enableShell ? green("enabled") : dim("disabled")}`);
@@ -6411,8 +8653,8 @@ var rules_exports = {};
6411
8653
  __export(rules_exports, {
6412
8654
  rulesCommand: () => rulesCommand
6413
8655
  });
6414
- import fs7 from "node:fs";
6415
- import path9 from "node:path";
8656
+ import fs8 from "node:fs";
8657
+ import path10 from "node:path";
6416
8658
  import yaml4 from "js-yaml";
6417
8659
  async function rulesCommand() {
6418
8660
  const configLoader = new ConfigLoader();
@@ -6423,18 +8665,18 @@ async function rulesCommand() {
6423
8665
  console.error("Failed to load configuration:", error.message);
6424
8666
  process.exit(1);
6425
8667
  }
6426
- const rulesPath = path9.resolve(config.security.rulesPath);
6427
- if (!fs7.existsSync(rulesPath)) {
8668
+ const rulesPath = path10.resolve(config.security.rulesPath);
8669
+ if (!fs8.existsSync(rulesPath)) {
6428
8670
  console.log(`Rules directory not found: ${rulesPath}`);
6429
8671
  console.log("No security rules loaded.");
6430
8672
  return;
6431
8673
  }
6432
- const stat = fs7.statSync(rulesPath);
8674
+ const stat = fs8.statSync(rulesPath);
6433
8675
  if (!stat.isDirectory()) {
6434
8676
  console.error(`Rules path is not a directory: ${rulesPath}`);
6435
8677
  process.exit(1);
6436
8678
  }
6437
- const files = fs7.readdirSync(rulesPath).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
8679
+ const files = fs8.readdirSync(rulesPath).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
6438
8680
  if (files.length === 0) {
6439
8681
  console.log(`No YAML rule files found in: ${rulesPath}`);
6440
8682
  return;
@@ -6443,9 +8685,9 @@ async function rulesCommand() {
6443
8685
  const allRules = [];
6444
8686
  const errors = [];
6445
8687
  for (const file of files) {
6446
- const filePath = path9.join(rulesPath, file);
8688
+ const filePath = path10.join(rulesPath, file);
6447
8689
  try {
6448
- const raw = fs7.readFileSync(filePath, "utf-8");
8690
+ const raw = fs8.readFileSync(filePath, "utf-8");
6449
8691
  const parsed = yaml4.load(raw);
6450
8692
  const rules = ruleLoader.loadFromObject(parsed);
6451
8693
  allRules.push(...rules);
@@ -6497,8 +8739,8 @@ var status_exports = {};
6497
8739
  __export(status_exports, {
6498
8740
  statusCommand: () => statusCommand
6499
8741
  });
6500
- import fs8 from "node:fs";
6501
- import path10 from "node:path";
8742
+ import fs9 from "node:fs";
8743
+ import path11 from "node:path";
6502
8744
  import yaml5 from "js-yaml";
6503
8745
  async function statusCommand() {
6504
8746
  const configLoader = new ConfigLoader();
@@ -6555,22 +8797,22 @@ async function statusCommand() {
6555
8797
  }
6556
8798
  console.log("");
6557
8799
  console.log("Storage:");
6558
- const dbPath = path10.resolve(config.storage.path);
6559
- const dbExists = fs8.existsSync(dbPath);
8800
+ const dbPath = path11.resolve(config.storage.path);
8801
+ const dbExists = fs9.existsSync(dbPath);
6560
8802
  console.log(` Database: ${dbPath}`);
6561
8803
  console.log(` Status: ${dbExists ? "exists" : "not yet created"}`);
6562
8804
  console.log("");
6563
- const rulesPath = path10.resolve(config.security.rulesPath);
8805
+ const rulesPath = path11.resolve(config.security.rulesPath);
6564
8806
  let ruleCount = 0;
6565
8807
  let ruleFileCount = 0;
6566
- if (fs8.existsSync(rulesPath) && fs8.statSync(rulesPath).isDirectory()) {
6567
- const files = fs8.readdirSync(rulesPath).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
8808
+ if (fs9.existsSync(rulesPath) && fs9.statSync(rulesPath).isDirectory()) {
8809
+ const files = fs9.readdirSync(rulesPath).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
6568
8810
  ruleFileCount = files.length;
6569
8811
  const ruleLoader = new RuleLoader();
6570
8812
  for (const file of files) {
6571
- const filePath = path10.join(rulesPath, file);
8813
+ const filePath = path11.join(rulesPath, file);
6572
8814
  try {
6573
- const raw = fs8.readFileSync(filePath, "utf-8");
8815
+ const raw = fs9.readFileSync(filePath, "utf-8");
6574
8816
  const parsed = yaml5.load(raw);
6575
8817
  const rules = ruleLoader.loadFromObject(parsed);
6576
8818
  ruleCount += rules.length;
@@ -6604,8 +8846,8 @@ var logs_exports = {};
6604
8846
  __export(logs_exports, {
6605
8847
  logsCommand: () => logsCommand
6606
8848
  });
6607
- import fs9 from "node:fs";
6608
- import path11 from "node:path";
8849
+ import fs10 from "node:fs";
8850
+ import path12 from "node:path";
6609
8851
  async function logsCommand(tail) {
6610
8852
  const configLoader = new ConfigLoader();
6611
8853
  let config;
@@ -6615,8 +8857,8 @@ async function logsCommand(tail) {
6615
8857
  console.error("Failed to load configuration:", error.message);
6616
8858
  process.exit(1);
6617
8859
  }
6618
- const dbPath = path11.resolve(config.storage.path);
6619
- if (!fs9.existsSync(dbPath)) {
8860
+ const dbPath = path12.resolve(config.storage.path);
8861
+ if (!fs10.existsSync(dbPath)) {
6620
8862
  console.log(`Database not found at: ${dbPath}`);
6621
8863
  console.log("No audit log entries. Alfred has not been run yet, or the database path is incorrect.");
6622
8864
  return;