@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.
- package/bundle/index.js +2471 -229
- 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
|
-
|
|
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(
|
|
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
|
|
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:
|
|
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: "
|
|
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/
|
|
4429
|
-
var
|
|
4430
|
-
|
|
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
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
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
|
-
|
|
4465
|
-
const
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
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
|
|
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
|
-
|
|
4473
|
-
|
|
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
|
-
|
|
4476
|
-
|
|
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
|
-
// ../
|
|
4483
|
-
var
|
|
4484
|
-
var
|
|
4485
|
-
"../
|
|
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
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
this.
|
|
4504
|
-
this.
|
|
4505
|
-
this.
|
|
4506
|
-
this.
|
|
4507
|
-
this.
|
|
4508
|
-
this.
|
|
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
|
-
|
|
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(
|
|
5809
|
+
const system = this.promptBuilder.buildSystemPrompt({
|
|
5810
|
+
memories,
|
|
5811
|
+
skills: skillMetas,
|
|
5812
|
+
userProfile
|
|
5813
|
+
});
|
|
4529
5814
|
const allMessages = this.promptBuilder.buildMessages(history);
|
|
4530
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
-
|
|
4743
|
-
if (this.
|
|
4744
|
-
|
|
4745
|
-
|
|
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
|
|
6346
|
+
async semanticSearch(userId, query, limit = 10) {
|
|
6347
|
+
if (!this.llm.supportsEmbeddings()) {
|
|
6348
|
+
return [];
|
|
6349
|
+
}
|
|
4750
6350
|
try {
|
|
4751
|
-
const
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
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 }, "
|
|
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
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
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
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
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
|
-
|
|
6780
|
+
const msgtype = event.content?.msgtype;
|
|
6781
|
+
if (!msgtype)
|
|
4979
6782
|
return;
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
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
|
|
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
|
|
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
|
|
5266
|
-
import
|
|
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
|
-
|
|
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,
|
|
7589
|
+
await adapter.editMessage(message.chatId, statusMessageId, formatted.text, sendOpts);
|
|
5426
7590
|
} catch {
|
|
5427
|
-
await adapter.sendMessage(message.chatId,
|
|
7591
|
+
await adapter.sendMessage(message.chatId, formatted.text, sendOpts);
|
|
5428
7592
|
}
|
|
5429
7593
|
} else {
|
|
5430
|
-
await adapter.sendMessage(message.chatId,
|
|
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 =
|
|
7616
|
+
const rulesPath = path8.resolve(this.config.security.rulesPath);
|
|
5453
7617
|
const rules = [];
|
|
5454
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
5467
|
-
const content =
|
|
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
|
|
5562
|
-
import
|
|
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 =
|
|
5594
|
-
if (
|
|
7760
|
+
const configPath = path9.join(projectRoot, "config", "default.yml");
|
|
7761
|
+
if (fs7.existsSync(configPath)) {
|
|
5595
7762
|
try {
|
|
5596
|
-
const parsed = yaml3.load(
|
|
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 =
|
|
5604
|
-
if (
|
|
7770
|
+
const envPath = path9.join(projectRoot, ".env");
|
|
7771
|
+
if (fs7.existsSync(envPath)) {
|
|
5605
7772
|
try {
|
|
5606
|
-
const lines =
|
|
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 =
|
|
5620
|
-
if (
|
|
7786
|
+
const rulesPath = path9.join(projectRoot, "config", "rules", "default-rules.yml");
|
|
7787
|
+
if (fs7.existsSync(rulesPath)) {
|
|
5621
7788
|
try {
|
|
5622
|
-
const rulesContent = yaml3.load(
|
|
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 =
|
|
5981
|
-
|
|
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 =
|
|
5984
|
-
if (!
|
|
5985
|
-
|
|
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 =
|
|
6051
|
-
|
|
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 =
|
|
6054
|
-
if (!
|
|
6055
|
-
|
|
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 =
|
|
6137
|
-
|
|
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 =
|
|
6140
|
-
if (!
|
|
6141
|
-
|
|
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
|
|
6415
|
-
import
|
|
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 =
|
|
6427
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
8688
|
+
const filePath = path10.join(rulesPath, file);
|
|
6447
8689
|
try {
|
|
6448
|
-
const raw =
|
|
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
|
|
6501
|
-
import
|
|
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 =
|
|
6559
|
-
const dbExists =
|
|
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 =
|
|
8805
|
+
const rulesPath = path11.resolve(config.security.rulesPath);
|
|
6564
8806
|
let ruleCount = 0;
|
|
6565
8807
|
let ruleFileCount = 0;
|
|
6566
|
-
if (
|
|
6567
|
-
const files =
|
|
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 =
|
|
8813
|
+
const filePath = path11.join(rulesPath, file);
|
|
6572
8814
|
try {
|
|
6573
|
-
const raw =
|
|
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
|
|
6608
|
-
import
|
|
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 =
|
|
6619
|
-
if (!
|
|
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;
|