@madh-io/alfred-ai 0.4.0 → 0.6.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 +2059 -534
- package/package.json +2 -1
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, AlfredConfigSchema;
|
|
15
15
|
var init_schema = __esm({
|
|
16
16
|
"../config/dist/schema.js"() {
|
|
17
17
|
"use strict";
|
|
@@ -80,6 +80,11 @@ 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
|
+
});
|
|
83
88
|
AlfredConfigSchema = z.object({
|
|
84
89
|
name: z.string(),
|
|
85
90
|
telegram: TelegramConfigSchema,
|
|
@@ -92,7 +97,8 @@ var init_schema = __esm({
|
|
|
92
97
|
logger: LoggerConfigSchema,
|
|
93
98
|
security: SecurityConfigSchema,
|
|
94
99
|
search: SearchConfigSchema.optional(),
|
|
95
|
-
email: EmailConfigSchema.optional()
|
|
100
|
+
email: EmailConfigSchema.optional(),
|
|
101
|
+
speech: SpeechConfigSchema.optional()
|
|
96
102
|
});
|
|
97
103
|
}
|
|
98
104
|
});
|
|
@@ -212,7 +218,10 @@ var init_loader = __esm({
|
|
|
212
218
|
ALFRED_SEARCH_API_KEY: ["search", "apiKey"],
|
|
213
219
|
ALFRED_SEARCH_BASE_URL: ["search", "baseUrl"],
|
|
214
220
|
ALFRED_EMAIL_USER: ["email", "auth", "user"],
|
|
215
|
-
ALFRED_EMAIL_PASS: ["email", "auth", "pass"]
|
|
221
|
+
ALFRED_EMAIL_PASS: ["email", "auth", "pass"],
|
|
222
|
+
ALFRED_SPEECH_PROVIDER: ["speech", "provider"],
|
|
223
|
+
ALFRED_SPEECH_API_KEY: ["speech", "apiKey"],
|
|
224
|
+
ALFRED_SPEECH_BASE_URL: ["speech", "baseUrl"]
|
|
216
225
|
};
|
|
217
226
|
ConfigLoader = class {
|
|
218
227
|
loadConfig(configPath) {
|
|
@@ -403,6 +412,25 @@ var init_migrations = __esm({
|
|
|
403
412
|
ON reminders(user_id, fired);
|
|
404
413
|
`);
|
|
405
414
|
}
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
version: 4,
|
|
418
|
+
description: "Add notes table for persistent note storage",
|
|
419
|
+
up(db) {
|
|
420
|
+
db.exec(`
|
|
421
|
+
CREATE TABLE IF NOT EXISTS notes (
|
|
422
|
+
id TEXT PRIMARY KEY,
|
|
423
|
+
user_id TEXT NOT NULL,
|
|
424
|
+
title TEXT NOT NULL,
|
|
425
|
+
content TEXT NOT NULL,
|
|
426
|
+
created_at TEXT NOT NULL,
|
|
427
|
+
updated_at TEXT NOT NULL
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
CREATE INDEX IF NOT EXISTS idx_notes_user
|
|
431
|
+
ON notes(user_id, updated_at DESC);
|
|
432
|
+
`);
|
|
433
|
+
}
|
|
406
434
|
}
|
|
407
435
|
];
|
|
408
436
|
}
|
|
@@ -850,6 +878,64 @@ var init_reminder_repository = __esm({
|
|
|
850
878
|
}
|
|
851
879
|
});
|
|
852
880
|
|
|
881
|
+
// ../storage/dist/repositories/note-repository.js
|
|
882
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
883
|
+
var NoteRepository;
|
|
884
|
+
var init_note_repository = __esm({
|
|
885
|
+
"../storage/dist/repositories/note-repository.js"() {
|
|
886
|
+
"use strict";
|
|
887
|
+
NoteRepository = class {
|
|
888
|
+
db;
|
|
889
|
+
constructor(db) {
|
|
890
|
+
this.db = db;
|
|
891
|
+
}
|
|
892
|
+
save(userId, title, content) {
|
|
893
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
894
|
+
const id = randomUUID3();
|
|
895
|
+
this.db.prepare("INSERT INTO notes (id, user_id, title, content, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)").run(id, userId, title, content, now, now);
|
|
896
|
+
return { id, userId, title, content, createdAt: now, updatedAt: now };
|
|
897
|
+
}
|
|
898
|
+
getById(noteId) {
|
|
899
|
+
const row = this.db.prepare("SELECT * FROM notes WHERE id = ?").get(noteId);
|
|
900
|
+
return row ? this.mapRow(row) : void 0;
|
|
901
|
+
}
|
|
902
|
+
list(userId, limit = 50) {
|
|
903
|
+
const rows = this.db.prepare("SELECT * FROM notes WHERE user_id = ? ORDER BY updated_at DESC LIMIT ?").all(userId, limit);
|
|
904
|
+
return rows.map((r) => this.mapRow(r));
|
|
905
|
+
}
|
|
906
|
+
search(userId, query) {
|
|
907
|
+
const pattern = `%${query}%`;
|
|
908
|
+
const rows = this.db.prepare("SELECT * FROM notes WHERE user_id = ? AND (title LIKE ? OR content LIKE ?) ORDER BY updated_at DESC").all(userId, pattern, pattern);
|
|
909
|
+
return rows.map((r) => this.mapRow(r));
|
|
910
|
+
}
|
|
911
|
+
update(noteId, title, content) {
|
|
912
|
+
const existing = this.getById(noteId);
|
|
913
|
+
if (!existing)
|
|
914
|
+
return void 0;
|
|
915
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
916
|
+
const newTitle = title ?? existing.title;
|
|
917
|
+
const newContent = content ?? existing.content;
|
|
918
|
+
this.db.prepare("UPDATE notes SET title = ?, content = ?, updated_at = ? WHERE id = ?").run(newTitle, newContent, now, noteId);
|
|
919
|
+
return { ...existing, title: newTitle, content: newContent, updatedAt: now };
|
|
920
|
+
}
|
|
921
|
+
delete(noteId) {
|
|
922
|
+
const result = this.db.prepare("DELETE FROM notes WHERE id = ?").run(noteId);
|
|
923
|
+
return result.changes > 0;
|
|
924
|
+
}
|
|
925
|
+
mapRow(row) {
|
|
926
|
+
return {
|
|
927
|
+
id: row.id,
|
|
928
|
+
userId: row.user_id,
|
|
929
|
+
title: row.title,
|
|
930
|
+
content: row.content,
|
|
931
|
+
createdAt: row.created_at,
|
|
932
|
+
updatedAt: row.updated_at
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
|
|
853
939
|
// ../storage/dist/index.js
|
|
854
940
|
var init_dist3 = __esm({
|
|
855
941
|
"../storage/dist/index.js"() {
|
|
@@ -862,6 +948,7 @@ var init_dist3 = __esm({
|
|
|
862
948
|
init_migrator();
|
|
863
949
|
init_migrations();
|
|
864
950
|
init_reminder_repository();
|
|
951
|
+
init_note_repository();
|
|
865
952
|
}
|
|
866
953
|
});
|
|
867
954
|
|
|
@@ -1004,6 +1091,15 @@ var init_anthropic = __esm({
|
|
|
1004
1091
|
switch (block.type) {
|
|
1005
1092
|
case "text":
|
|
1006
1093
|
return { type: "text", text: block.text };
|
|
1094
|
+
case "image":
|
|
1095
|
+
return {
|
|
1096
|
+
type: "image",
|
|
1097
|
+
source: {
|
|
1098
|
+
type: "base64",
|
|
1099
|
+
media_type: block.source.media_type,
|
|
1100
|
+
data: block.source.data
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1007
1103
|
case "tool_use":
|
|
1008
1104
|
return {
|
|
1009
1105
|
type: "tool_use",
|
|
@@ -1200,6 +1296,14 @@ var init_openai = __esm({
|
|
|
1200
1296
|
case "text":
|
|
1201
1297
|
textParts.push({ type: "text", text: block.text });
|
|
1202
1298
|
break;
|
|
1299
|
+
case "image":
|
|
1300
|
+
textParts.push({
|
|
1301
|
+
type: "image_url",
|
|
1302
|
+
image_url: {
|
|
1303
|
+
url: `data:${block.source.media_type};base64,${block.source.data}`
|
|
1304
|
+
}
|
|
1305
|
+
});
|
|
1306
|
+
break;
|
|
1203
1307
|
case "tool_use":
|
|
1204
1308
|
toolUseParts.push({
|
|
1205
1309
|
id: block.id,
|
|
@@ -1558,11 +1662,15 @@ var init_ollama = __esm({
|
|
|
1558
1662
|
}
|
|
1559
1663
|
mapContentBlocks(role, blocks) {
|
|
1560
1664
|
const textParts = [];
|
|
1665
|
+
const images = [];
|
|
1561
1666
|
for (const block of blocks) {
|
|
1562
1667
|
switch (block.type) {
|
|
1563
1668
|
case "text":
|
|
1564
1669
|
textParts.push(block.text);
|
|
1565
1670
|
break;
|
|
1671
|
+
case "image":
|
|
1672
|
+
images.push(block.source.data);
|
|
1673
|
+
break;
|
|
1566
1674
|
case "tool_use":
|
|
1567
1675
|
textParts.push(`[Tool call: ${block.name}(${JSON.stringify(block.input)})]`);
|
|
1568
1676
|
break;
|
|
@@ -1571,7 +1679,11 @@ var init_ollama = __esm({
|
|
|
1571
1679
|
break;
|
|
1572
1680
|
}
|
|
1573
1681
|
}
|
|
1574
|
-
|
|
1682
|
+
const msg = { role, content: textParts.join("\n") };
|
|
1683
|
+
if (images.length > 0) {
|
|
1684
|
+
msg.images = images;
|
|
1685
|
+
}
|
|
1686
|
+
return msg;
|
|
1575
1687
|
}
|
|
1576
1688
|
mapTools(tools) {
|
|
1577
1689
|
return tools.map((tool) => ({
|
|
@@ -1647,6 +1759,9 @@ function estimateMessageTokens(msg) {
|
|
|
1647
1759
|
case "text":
|
|
1648
1760
|
tokens += estimateTokens(block.text);
|
|
1649
1761
|
break;
|
|
1762
|
+
case "image":
|
|
1763
|
+
tokens += 1e3;
|
|
1764
|
+
break;
|
|
1650
1765
|
case "tool_use":
|
|
1651
1766
|
tokens += estimateTokens(block.name) + estimateTokens(JSON.stringify(block.input));
|
|
1652
1767
|
break;
|
|
@@ -1663,9 +1778,9 @@ var init_prompt_builder = __esm({
|
|
|
1663
1778
|
"use strict";
|
|
1664
1779
|
PromptBuilder = class {
|
|
1665
1780
|
buildSystemPrompt(memories, skills) {
|
|
1666
|
-
const
|
|
1781
|
+
const os3 = process.platform === "darwin" ? "macOS" : process.platform === "win32" ? "Windows" : "Linux";
|
|
1667
1782
|
const homeDir = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
1668
|
-
let prompt = `You are Alfred, a personal AI assistant. You run on ${
|
|
1783
|
+
let prompt = `You are Alfred, a personal AI assistant. You run on ${os3} (home: ${homeDir}).
|
|
1669
1784
|
|
|
1670
1785
|
## Core principles
|
|
1671
1786
|
- ACT, don't just talk. When the user asks you to do something, USE YOUR TOOLS immediately. Never say "I could do X" \u2014 just do X.
|
|
@@ -1681,7 +1796,7 @@ For complex tasks, work through multiple steps:
|
|
|
1681
1796
|
4. **Summarize** the final result clearly.
|
|
1682
1797
|
|
|
1683
1798
|
## Environment
|
|
1684
|
-
- OS: ${
|
|
1799
|
+
- OS: ${os3}
|
|
1685
1800
|
- Home: ${homeDir}
|
|
1686
1801
|
- Documents: ${homeDir}/Documents
|
|
1687
1802
|
- Desktop: ${homeDir}/Desktop
|
|
@@ -2692,18 +2807,18 @@ ${reminderList.map((r) => `- ${r.reminderId}: "${r.message}" (triggers at ${r.tr
|
|
|
2692
2807
|
});
|
|
2693
2808
|
|
|
2694
2809
|
// ../skills/dist/built-in/note.js
|
|
2695
|
-
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
2696
2810
|
var NoteSkill;
|
|
2697
2811
|
var init_note = __esm({
|
|
2698
2812
|
"../skills/dist/built-in/note.js"() {
|
|
2699
2813
|
"use strict";
|
|
2700
2814
|
init_skill();
|
|
2701
2815
|
NoteSkill = class extends Skill {
|
|
2816
|
+
noteRepo;
|
|
2702
2817
|
metadata = {
|
|
2703
2818
|
name: "note",
|
|
2704
|
-
description: "Save, list, search, or delete persistent notes. Use when the user wants to write down or retrieve text notes, lists, or ideas.",
|
|
2819
|
+
description: "Save, list, search, or delete persistent notes (stored in SQLite). Use when the user wants to write down or retrieve text notes, lists, or ideas.",
|
|
2705
2820
|
riskLevel: "write",
|
|
2706
|
-
version: "
|
|
2821
|
+
version: "2.0.0",
|
|
2707
2822
|
inputSchema: {
|
|
2708
2823
|
type: "object",
|
|
2709
2824
|
properties: {
|
|
@@ -2732,7 +2847,10 @@ var init_note = __esm({
|
|
|
2732
2847
|
required: ["action"]
|
|
2733
2848
|
}
|
|
2734
2849
|
};
|
|
2735
|
-
|
|
2850
|
+
constructor(noteRepo) {
|
|
2851
|
+
super();
|
|
2852
|
+
this.noteRepo = noteRepo;
|
|
2853
|
+
}
|
|
2736
2854
|
async execute(input2, context) {
|
|
2737
2855
|
const action = input2.action;
|
|
2738
2856
|
switch (action) {
|
|
@@ -2755,394 +2873,101 @@ var init_note = __esm({
|
|
|
2755
2873
|
const title = input2.title;
|
|
2756
2874
|
const content = input2.content;
|
|
2757
2875
|
if (!title || typeof title !== "string") {
|
|
2758
|
-
return {
|
|
2759
|
-
success: false,
|
|
2760
|
-
error: 'Missing required field "title" for save action'
|
|
2761
|
-
};
|
|
2876
|
+
return { success: false, error: 'Missing required field "title" for save action' };
|
|
2762
2877
|
}
|
|
2763
2878
|
if (!content || typeof content !== "string") {
|
|
2764
|
-
return {
|
|
2765
|
-
success: false,
|
|
2766
|
-
error: 'Missing required field "content" for save action'
|
|
2767
|
-
};
|
|
2879
|
+
return { success: false, error: 'Missing required field "content" for save action' };
|
|
2768
2880
|
}
|
|
2769
|
-
const
|
|
2770
|
-
const createdAt = Date.now();
|
|
2771
|
-
this.notes.set(noteId, {
|
|
2772
|
-
noteId,
|
|
2773
|
-
userId: context.userId,
|
|
2774
|
-
title,
|
|
2775
|
-
content,
|
|
2776
|
-
createdAt
|
|
2777
|
-
});
|
|
2881
|
+
const entry = this.noteRepo.save(context.userId, title, content);
|
|
2778
2882
|
return {
|
|
2779
2883
|
success: true,
|
|
2780
|
-
data: { noteId, title
|
|
2781
|
-
display: `Note saved
|
|
2884
|
+
data: { noteId: entry.id, title: entry.title },
|
|
2885
|
+
display: `Note saved: "${title}"`
|
|
2782
2886
|
};
|
|
2783
2887
|
}
|
|
2784
2888
|
listNotes(context) {
|
|
2785
|
-
const
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
userNotes.push({
|
|
2789
|
-
noteId: entry.noteId,
|
|
2790
|
-
title: entry.title,
|
|
2791
|
-
createdAt: entry.createdAt
|
|
2792
|
-
});
|
|
2793
|
-
}
|
|
2889
|
+
const notes = this.noteRepo.list(context.userId);
|
|
2890
|
+
if (notes.length === 0) {
|
|
2891
|
+
return { success: true, data: [], display: "No notes found." };
|
|
2794
2892
|
}
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
${userNotes.map((n) => `- ${n.noteId}: "${n.title}"`).join("\n")}`
|
|
2800
|
-
};
|
|
2893
|
+
const display = notes.map((n) => `- **${n.title}** (${n.id.slice(0, 8)}\u2026)
|
|
2894
|
+
${n.content.slice(0, 100)}${n.content.length > 100 ? "\u2026" : ""}`).join("\n");
|
|
2895
|
+
return { success: true, data: notes, display: `${notes.length} note(s):
|
|
2896
|
+
${display}` };
|
|
2801
2897
|
}
|
|
2802
2898
|
searchNotes(input2, context) {
|
|
2803
2899
|
const query = input2.query;
|
|
2804
2900
|
if (!query || typeof query !== "string") {
|
|
2805
|
-
return {
|
|
2806
|
-
success: false,
|
|
2807
|
-
error: 'Missing required field "query" for search action'
|
|
2808
|
-
};
|
|
2901
|
+
return { success: false, error: 'Missing required field "query" for search action' };
|
|
2809
2902
|
}
|
|
2810
|
-
const
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
if (entry.userId !== context.userId) {
|
|
2814
|
-
continue;
|
|
2815
|
-
}
|
|
2816
|
-
if (entry.title.toLowerCase().includes(lowerQuery) || entry.content.toLowerCase().includes(lowerQuery)) {
|
|
2817
|
-
matches.push({
|
|
2818
|
-
noteId: entry.noteId,
|
|
2819
|
-
title: entry.title,
|
|
2820
|
-
content: entry.content
|
|
2821
|
-
});
|
|
2822
|
-
}
|
|
2903
|
+
const matches = this.noteRepo.search(context.userId, query);
|
|
2904
|
+
if (matches.length === 0) {
|
|
2905
|
+
return { success: true, data: [], display: `No notes matching "${query}".` };
|
|
2823
2906
|
}
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
${matches.map((n) => `- ${n.noteId}: "${n.title}"`).join("\n")}`
|
|
2829
|
-
};
|
|
2907
|
+
const display = matches.map((n) => `- **${n.title}** (${n.id.slice(0, 8)}\u2026)
|
|
2908
|
+
${n.content.slice(0, 100)}${n.content.length > 100 ? "\u2026" : ""}`).join("\n");
|
|
2909
|
+
return { success: true, data: matches, display: `Found ${matches.length} note(s):
|
|
2910
|
+
${display}` };
|
|
2830
2911
|
}
|
|
2831
2912
|
deleteNote(input2) {
|
|
2832
2913
|
const noteId = input2.noteId;
|
|
2833
2914
|
if (!noteId || typeof noteId !== "string") {
|
|
2834
|
-
return {
|
|
2835
|
-
success: false,
|
|
2836
|
-
error: 'Missing required field "noteId" for delete action'
|
|
2837
|
-
};
|
|
2838
|
-
}
|
|
2839
|
-
const entry = this.notes.get(noteId);
|
|
2840
|
-
if (!entry) {
|
|
2841
|
-
return {
|
|
2842
|
-
success: false,
|
|
2843
|
-
error: `Note "${noteId}" not found`
|
|
2844
|
-
};
|
|
2845
|
-
}
|
|
2846
|
-
this.notes.delete(noteId);
|
|
2847
|
-
return {
|
|
2848
|
-
success: true,
|
|
2849
|
-
data: { noteId },
|
|
2850
|
-
display: `Note "${noteId}" deleted.`
|
|
2851
|
-
};
|
|
2852
|
-
}
|
|
2853
|
-
};
|
|
2854
|
-
}
|
|
2855
|
-
});
|
|
2856
|
-
|
|
2857
|
-
// ../skills/dist/built-in/summarize.js
|
|
2858
|
-
var DEFAULT_MAX_LENGTH, SummarizeSkill;
|
|
2859
|
-
var init_summarize = __esm({
|
|
2860
|
-
"../skills/dist/built-in/summarize.js"() {
|
|
2861
|
-
"use strict";
|
|
2862
|
-
init_skill();
|
|
2863
|
-
DEFAULT_MAX_LENGTH = 280;
|
|
2864
|
-
SummarizeSkill = class extends Skill {
|
|
2865
|
-
metadata = {
|
|
2866
|
-
name: "summarize",
|
|
2867
|
-
description: "Produce an extractive summary of the given text",
|
|
2868
|
-
riskLevel: "read",
|
|
2869
|
-
version: "1.0.0",
|
|
2870
|
-
inputSchema: {
|
|
2871
|
-
type: "object",
|
|
2872
|
-
properties: {
|
|
2873
|
-
text: {
|
|
2874
|
-
type: "string",
|
|
2875
|
-
description: "The text to summarize"
|
|
2876
|
-
},
|
|
2877
|
-
maxLength: {
|
|
2878
|
-
type: "number",
|
|
2879
|
-
description: "Maximum character length for the summary (default: 280)"
|
|
2880
|
-
}
|
|
2881
|
-
},
|
|
2882
|
-
required: ["text"]
|
|
2883
|
-
}
|
|
2884
|
-
};
|
|
2885
|
-
async execute(input2, _context) {
|
|
2886
|
-
const text = input2.text;
|
|
2887
|
-
const maxLength = input2.maxLength ?? DEFAULT_MAX_LENGTH;
|
|
2888
|
-
if (!text || typeof text !== "string") {
|
|
2889
|
-
return {
|
|
2890
|
-
success: false,
|
|
2891
|
-
error: 'Invalid input: "text" must be a non-empty string'
|
|
2892
|
-
};
|
|
2893
|
-
}
|
|
2894
|
-
if (text.length <= maxLength) {
|
|
2895
|
-
return {
|
|
2896
|
-
success: true,
|
|
2897
|
-
data: { summary: text },
|
|
2898
|
-
display: text
|
|
2899
|
-
};
|
|
2900
|
-
}
|
|
2901
|
-
const summary = this.extractiveSummarize(text, maxLength);
|
|
2902
|
-
return {
|
|
2903
|
-
success: true,
|
|
2904
|
-
data: { summary },
|
|
2905
|
-
display: summary
|
|
2906
|
-
};
|
|
2907
|
-
}
|
|
2908
|
-
extractiveSummarize(text, maxLength) {
|
|
2909
|
-
const sentences = text.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
2910
|
-
if (sentences.length === 0) {
|
|
2911
|
-
return text.slice(0, maxLength);
|
|
2912
|
-
}
|
|
2913
|
-
const wordFrequency = this.buildWordFrequency(text);
|
|
2914
|
-
const scored = sentences.map((sentence, index) => ({
|
|
2915
|
-
sentence,
|
|
2916
|
-
index,
|
|
2917
|
-
score: this.scoreSentence(sentence, wordFrequency)
|
|
2918
|
-
}));
|
|
2919
|
-
const ranked = [...scored].sort((a, b) => b.score - a.score);
|
|
2920
|
-
const selected = [];
|
|
2921
|
-
let currentLength = 0;
|
|
2922
|
-
for (const entry of ranked) {
|
|
2923
|
-
const addition = currentLength === 0 ? entry.sentence.length : entry.sentence.length + 1;
|
|
2924
|
-
if (currentLength + addition > maxLength) {
|
|
2925
|
-
continue;
|
|
2926
|
-
}
|
|
2927
|
-
selected.push(entry);
|
|
2928
|
-
currentLength += addition;
|
|
2929
|
-
}
|
|
2930
|
-
if (selected.length === 0) {
|
|
2931
|
-
return sentences[0].slice(0, maxLength);
|
|
2932
|
-
}
|
|
2933
|
-
selected.sort((a, b) => a.index - b.index);
|
|
2934
|
-
return selected.map((s) => s.sentence).join(" ");
|
|
2935
|
-
}
|
|
2936
|
-
buildWordFrequency(text) {
|
|
2937
|
-
const stopWords = /* @__PURE__ */ new Set([
|
|
2938
|
-
"the",
|
|
2939
|
-
"a",
|
|
2940
|
-
"an",
|
|
2941
|
-
"is",
|
|
2942
|
-
"are",
|
|
2943
|
-
"was",
|
|
2944
|
-
"were",
|
|
2945
|
-
"be",
|
|
2946
|
-
"been",
|
|
2947
|
-
"being",
|
|
2948
|
-
"have",
|
|
2949
|
-
"has",
|
|
2950
|
-
"had",
|
|
2951
|
-
"do",
|
|
2952
|
-
"does",
|
|
2953
|
-
"did",
|
|
2954
|
-
"will",
|
|
2955
|
-
"would",
|
|
2956
|
-
"could",
|
|
2957
|
-
"should",
|
|
2958
|
-
"may",
|
|
2959
|
-
"might",
|
|
2960
|
-
"shall",
|
|
2961
|
-
"can",
|
|
2962
|
-
"to",
|
|
2963
|
-
"of",
|
|
2964
|
-
"in",
|
|
2965
|
-
"for",
|
|
2966
|
-
"on",
|
|
2967
|
-
"with",
|
|
2968
|
-
"at",
|
|
2969
|
-
"by",
|
|
2970
|
-
"from",
|
|
2971
|
-
"as",
|
|
2972
|
-
"into",
|
|
2973
|
-
"through",
|
|
2974
|
-
"during",
|
|
2975
|
-
"before",
|
|
2976
|
-
"after",
|
|
2977
|
-
"and",
|
|
2978
|
-
"but",
|
|
2979
|
-
"or",
|
|
2980
|
-
"nor",
|
|
2981
|
-
"not",
|
|
2982
|
-
"so",
|
|
2983
|
-
"yet",
|
|
2984
|
-
"both",
|
|
2985
|
-
"either",
|
|
2986
|
-
"neither",
|
|
2987
|
-
"each",
|
|
2988
|
-
"every",
|
|
2989
|
-
"all",
|
|
2990
|
-
"any",
|
|
2991
|
-
"few",
|
|
2992
|
-
"more",
|
|
2993
|
-
"most",
|
|
2994
|
-
"other",
|
|
2995
|
-
"some",
|
|
2996
|
-
"such",
|
|
2997
|
-
"no",
|
|
2998
|
-
"only",
|
|
2999
|
-
"own",
|
|
3000
|
-
"same",
|
|
3001
|
-
"than",
|
|
3002
|
-
"too",
|
|
3003
|
-
"very",
|
|
3004
|
-
"just",
|
|
3005
|
-
"because",
|
|
3006
|
-
"if",
|
|
3007
|
-
"when",
|
|
3008
|
-
"where",
|
|
3009
|
-
"how",
|
|
3010
|
-
"what",
|
|
3011
|
-
"which",
|
|
3012
|
-
"who",
|
|
3013
|
-
"whom",
|
|
3014
|
-
"this",
|
|
3015
|
-
"that",
|
|
3016
|
-
"these",
|
|
3017
|
-
"those",
|
|
3018
|
-
"it",
|
|
3019
|
-
"its",
|
|
3020
|
-
"i",
|
|
3021
|
-
"me",
|
|
3022
|
-
"my",
|
|
3023
|
-
"we",
|
|
3024
|
-
"our",
|
|
3025
|
-
"you",
|
|
3026
|
-
"your",
|
|
3027
|
-
"he",
|
|
3028
|
-
"him",
|
|
3029
|
-
"his",
|
|
3030
|
-
"she",
|
|
3031
|
-
"her",
|
|
3032
|
-
"they",
|
|
3033
|
-
"them",
|
|
3034
|
-
"their"
|
|
3035
|
-
]);
|
|
3036
|
-
const frequency = /* @__PURE__ */ new Map();
|
|
3037
|
-
const words = text.toLowerCase().match(/\b[a-z]+\b/g) ?? [];
|
|
3038
|
-
for (const word of words) {
|
|
3039
|
-
if (stopWords.has(word) || word.length < 3) {
|
|
3040
|
-
continue;
|
|
3041
|
-
}
|
|
3042
|
-
frequency.set(word, (frequency.get(word) ?? 0) + 1);
|
|
3043
|
-
}
|
|
3044
|
-
return frequency;
|
|
3045
|
-
}
|
|
3046
|
-
scoreSentence(sentence, wordFrequency) {
|
|
3047
|
-
const words = sentence.toLowerCase().match(/\b[a-z]+\b/g) ?? [];
|
|
3048
|
-
let score = 0;
|
|
3049
|
-
for (const word of words) {
|
|
3050
|
-
score += wordFrequency.get(word) ?? 0;
|
|
3051
|
-
}
|
|
3052
|
-
return score;
|
|
3053
|
-
}
|
|
3054
|
-
};
|
|
3055
|
-
}
|
|
3056
|
-
});
|
|
3057
|
-
|
|
3058
|
-
// ../skills/dist/built-in/translate.js
|
|
3059
|
-
var TranslateSkill;
|
|
3060
|
-
var init_translate = __esm({
|
|
3061
|
-
"../skills/dist/built-in/translate.js"() {
|
|
3062
|
-
"use strict";
|
|
3063
|
-
init_skill();
|
|
3064
|
-
TranslateSkill = class extends Skill {
|
|
3065
|
-
metadata = {
|
|
3066
|
-
name: "translate",
|
|
3067
|
-
description: "Translate text between languages (placeholder \u2014 requires external API)",
|
|
3068
|
-
riskLevel: "read",
|
|
3069
|
-
version: "0.1.0",
|
|
3070
|
-
inputSchema: {
|
|
3071
|
-
type: "object",
|
|
3072
|
-
properties: {
|
|
3073
|
-
text: {
|
|
3074
|
-
type: "string",
|
|
3075
|
-
description: "The text to translate"
|
|
3076
|
-
},
|
|
3077
|
-
targetLanguage: {
|
|
3078
|
-
type: "string",
|
|
3079
|
-
description: 'The language to translate into (e.g. "es", "fr", "de")'
|
|
3080
|
-
},
|
|
3081
|
-
sourceLanguage: {
|
|
3082
|
-
type: "string",
|
|
3083
|
-
description: "The source language (optional, auto-detected if omitted)"
|
|
3084
|
-
}
|
|
3085
|
-
},
|
|
3086
|
-
required: ["text", "targetLanguage"]
|
|
2915
|
+
return { success: false, error: 'Missing required field "noteId" for delete action' };
|
|
3087
2916
|
}
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
const targetLanguage = input2.targetLanguage;
|
|
3092
|
-
const sourceLanguage = input2.sourceLanguage;
|
|
3093
|
-
if (!text || typeof text !== "string") {
|
|
3094
|
-
return {
|
|
3095
|
-
success: false,
|
|
3096
|
-
error: 'Invalid input: "text" must be a non-empty string'
|
|
3097
|
-
};
|
|
3098
|
-
}
|
|
3099
|
-
if (!targetLanguage || typeof targetLanguage !== "string") {
|
|
3100
|
-
return {
|
|
3101
|
-
success: false,
|
|
3102
|
-
error: 'Invalid input: "targetLanguage" must be a non-empty string'
|
|
3103
|
-
};
|
|
2917
|
+
const deleted = this.noteRepo.delete(noteId);
|
|
2918
|
+
if (!deleted) {
|
|
2919
|
+
return { success: false, error: `Note "${noteId}" not found` };
|
|
3104
2920
|
}
|
|
3105
|
-
|
|
3106
|
-
return {
|
|
3107
|
-
success: true,
|
|
3108
|
-
data: {
|
|
3109
|
-
note: "Translation is not yet connected to a translation API",
|
|
3110
|
-
text,
|
|
3111
|
-
targetLanguage,
|
|
3112
|
-
sourceLanguage: sourceLanguage ?? "auto"
|
|
3113
|
-
},
|
|
3114
|
-
display: `Translation${sourceLabel} to "${targetLanguage}" is not yet implemented. This skill will be connected to a translation API in a future update.
|
|
3115
|
-
|
|
3116
|
-
Requested text: "${text}"`
|
|
3117
|
-
};
|
|
2921
|
+
return { success: true, data: { noteId }, display: `Note deleted.` };
|
|
3118
2922
|
}
|
|
3119
2923
|
};
|
|
3120
2924
|
}
|
|
3121
2925
|
});
|
|
3122
2926
|
|
|
3123
2927
|
// ../skills/dist/built-in/weather.js
|
|
3124
|
-
var WeatherSkill;
|
|
2928
|
+
var WEATHER_CODES, WeatherSkill;
|
|
3125
2929
|
var init_weather = __esm({
|
|
3126
2930
|
"../skills/dist/built-in/weather.js"() {
|
|
3127
2931
|
"use strict";
|
|
3128
2932
|
init_skill();
|
|
2933
|
+
WEATHER_CODES = {
|
|
2934
|
+
0: "Clear sky",
|
|
2935
|
+
1: "Mainly clear",
|
|
2936
|
+
2: "Partly cloudy",
|
|
2937
|
+
3: "Overcast",
|
|
2938
|
+
45: "Foggy",
|
|
2939
|
+
48: "Depositing rime fog",
|
|
2940
|
+
51: "Light drizzle",
|
|
2941
|
+
53: "Moderate drizzle",
|
|
2942
|
+
55: "Dense drizzle",
|
|
2943
|
+
61: "Slight rain",
|
|
2944
|
+
63: "Moderate rain",
|
|
2945
|
+
65: "Heavy rain",
|
|
2946
|
+
71: "Slight snow",
|
|
2947
|
+
73: "Moderate snow",
|
|
2948
|
+
75: "Heavy snow",
|
|
2949
|
+
77: "Snow grains",
|
|
2950
|
+
80: "Slight rain showers",
|
|
2951
|
+
81: "Moderate rain showers",
|
|
2952
|
+
82: "Violent rain showers",
|
|
2953
|
+
85: "Slight snow showers",
|
|
2954
|
+
86: "Heavy snow showers",
|
|
2955
|
+
95: "Thunderstorm",
|
|
2956
|
+
96: "Thunderstorm with slight hail",
|
|
2957
|
+
99: "Thunderstorm with heavy hail"
|
|
2958
|
+
};
|
|
3129
2959
|
WeatherSkill = class extends Skill {
|
|
3130
2960
|
metadata = {
|
|
3131
2961
|
name: "weather",
|
|
3132
|
-
description: "Get weather
|
|
2962
|
+
description: "Get current weather for any location. Uses Open-Meteo (free, no API key). Use when the user asks about weather, temperature, or conditions somewhere.",
|
|
3133
2963
|
riskLevel: "read",
|
|
3134
|
-
version: "0.
|
|
2964
|
+
version: "2.0.0",
|
|
3135
2965
|
inputSchema: {
|
|
3136
2966
|
type: "object",
|
|
3137
2967
|
properties: {
|
|
3138
2968
|
location: {
|
|
3139
2969
|
type: "string",
|
|
3140
|
-
description: '
|
|
3141
|
-
},
|
|
3142
|
-
units: {
|
|
3143
|
-
type: "string",
|
|
3144
|
-
enum: ["metric", "imperial"],
|
|
3145
|
-
description: "Unit system for temperature (default: metric)"
|
|
2970
|
+
description: 'City or place name (e.g. "Vienna", "New York", "Tokyo")'
|
|
3146
2971
|
}
|
|
3147
2972
|
},
|
|
3148
2973
|
required: ["location"]
|
|
@@ -3150,22 +2975,49 @@ var init_weather = __esm({
|
|
|
3150
2975
|
};
|
|
3151
2976
|
async execute(input2, _context) {
|
|
3152
2977
|
const location = input2.location;
|
|
3153
|
-
const units = input2.units ?? "metric";
|
|
3154
2978
|
if (!location || typeof location !== "string") {
|
|
3155
|
-
return {
|
|
3156
|
-
|
|
3157
|
-
|
|
2979
|
+
return { success: false, error: 'Missing required field "location"' };
|
|
2980
|
+
}
|
|
2981
|
+
try {
|
|
2982
|
+
const geo = await this.geocode(location);
|
|
2983
|
+
if (!geo) {
|
|
2984
|
+
return { success: false, error: `Location "${location}" not found` };
|
|
2985
|
+
}
|
|
2986
|
+
const weather = await this.fetchWeather(geo.latitude, geo.longitude);
|
|
2987
|
+
const condition = WEATHER_CODES[weather.weathercode] ?? `Code ${weather.weathercode}`;
|
|
2988
|
+
const locationLabel = geo.admin1 ? `${geo.name}, ${geo.admin1}, ${geo.country}` : `${geo.name}, ${geo.country}`;
|
|
2989
|
+
const data = {
|
|
2990
|
+
location: locationLabel,
|
|
2991
|
+
temperature: weather.temperature,
|
|
2992
|
+
unit: "\xB0C",
|
|
2993
|
+
condition,
|
|
2994
|
+
windSpeed: weather.windspeed,
|
|
2995
|
+
windDirection: weather.winddirection,
|
|
2996
|
+
isDay: weather.is_day === 1
|
|
3158
2997
|
};
|
|
2998
|
+
const display = `${locationLabel}: ${weather.temperature}\xB0C, ${condition}
|
|
2999
|
+
Wind: ${weather.windspeed} km/h`;
|
|
3000
|
+
return { success: true, data, display };
|
|
3001
|
+
} catch (err) {
|
|
3002
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3003
|
+
return { success: false, error: `Weather fetch failed: ${msg}` };
|
|
3159
3004
|
}
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3005
|
+
}
|
|
3006
|
+
async geocode(query) {
|
|
3007
|
+
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(query)}&count=1&language=en&format=json`;
|
|
3008
|
+
const res = await fetch(url);
|
|
3009
|
+
if (!res.ok)
|
|
3010
|
+
throw new Error(`Geocoding API returned ${res.status}`);
|
|
3011
|
+
const data = await res.json();
|
|
3012
|
+
return data.results?.[0];
|
|
3013
|
+
}
|
|
3014
|
+
async fetchWeather(lat, lon) {
|
|
3015
|
+
const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t_weather=true&timezone=auto`;
|
|
3016
|
+
const res = await fetch(url);
|
|
3017
|
+
if (!res.ok)
|
|
3018
|
+
throw new Error(`Weather API returned ${res.status}`);
|
|
3019
|
+
const data = await res.json();
|
|
3020
|
+
return data.current_weather;
|
|
3169
3021
|
}
|
|
3170
3022
|
};
|
|
3171
3023
|
}
|
|
@@ -3420,38 +3272,45 @@ ${entries.map((e) => `- [${e.category}] ${e.key}: "${e.value}"`).join("\n")}`
|
|
|
3420
3272
|
});
|
|
3421
3273
|
|
|
3422
3274
|
// ../skills/dist/built-in/delegate.js
|
|
3423
|
-
var DelegateSkill;
|
|
3275
|
+
var MAX_SUB_AGENT_ITERATIONS, DelegateSkill;
|
|
3424
3276
|
var init_delegate = __esm({
|
|
3425
3277
|
"../skills/dist/built-in/delegate.js"() {
|
|
3426
3278
|
"use strict";
|
|
3427
3279
|
init_skill();
|
|
3280
|
+
MAX_SUB_AGENT_ITERATIONS = 5;
|
|
3428
3281
|
DelegateSkill = class extends Skill {
|
|
3429
3282
|
llm;
|
|
3283
|
+
skillRegistry;
|
|
3284
|
+
skillSandbox;
|
|
3285
|
+
securityManager;
|
|
3430
3286
|
metadata = {
|
|
3431
3287
|
name: "delegate",
|
|
3432
|
-
description:
|
|
3288
|
+
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.',
|
|
3433
3289
|
riskLevel: "write",
|
|
3434
|
-
version: "
|
|
3290
|
+
version: "2.0.0",
|
|
3435
3291
|
inputSchema: {
|
|
3436
3292
|
type: "object",
|
|
3437
3293
|
properties: {
|
|
3438
3294
|
task: {
|
|
3439
3295
|
type: "string",
|
|
3440
|
-
description: "The task to delegate to
|
|
3296
|
+
description: "The task to delegate to the sub-agent. Be specific about what you want."
|
|
3441
3297
|
},
|
|
3442
3298
|
context: {
|
|
3443
3299
|
type: "string",
|
|
3444
|
-
description: "Additional context
|
|
3300
|
+
description: "Additional context the sub-agent needs (optional)"
|
|
3445
3301
|
}
|
|
3446
3302
|
},
|
|
3447
3303
|
required: ["task"]
|
|
3448
3304
|
}
|
|
3449
3305
|
};
|
|
3450
|
-
constructor(llm) {
|
|
3306
|
+
constructor(llm, skillRegistry, skillSandbox, securityManager) {
|
|
3451
3307
|
super();
|
|
3452
3308
|
this.llm = llm;
|
|
3309
|
+
this.skillRegistry = skillRegistry;
|
|
3310
|
+
this.skillSandbox = skillSandbox;
|
|
3311
|
+
this.securityManager = securityManager;
|
|
3453
3312
|
}
|
|
3454
|
-
async execute(input2,
|
|
3313
|
+
async execute(input2, context) {
|
|
3455
3314
|
const task = input2.task;
|
|
3456
3315
|
const additionalContext = input2.context;
|
|
3457
3316
|
if (!task || typeof task !== "string") {
|
|
@@ -3460,7 +3319,8 @@ var init_delegate = __esm({
|
|
|
3460
3319
|
error: 'Missing required field "task"'
|
|
3461
3320
|
};
|
|
3462
3321
|
}
|
|
3463
|
-
const
|
|
3322
|
+
const tools = this.buildSubAgentTools();
|
|
3323
|
+
const systemPrompt = "You are a sub-agent of Alfred, a personal AI assistant. Complete the assigned task using the tools available to you. Work step by step: use tools to gather information, then synthesize a clear result. Be concise and return only the final answer when done.";
|
|
3464
3324
|
let userContent = task;
|
|
3465
3325
|
if (additionalContext && typeof additionalContext === "string") {
|
|
3466
3326
|
userContent = `${task}
|
|
@@ -3468,22 +3328,58 @@ var init_delegate = __esm({
|
|
|
3468
3328
|
Additional context: ${additionalContext}`;
|
|
3469
3329
|
}
|
|
3470
3330
|
const messages = [
|
|
3471
|
-
{
|
|
3472
|
-
role: "user",
|
|
3473
|
-
content: userContent
|
|
3474
|
-
}
|
|
3331
|
+
{ role: "user", content: userContent }
|
|
3475
3332
|
];
|
|
3476
3333
|
try {
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3334
|
+
let iteration = 0;
|
|
3335
|
+
let totalInputTokens = 0;
|
|
3336
|
+
let totalOutputTokens = 0;
|
|
3337
|
+
while (true) {
|
|
3338
|
+
const response = await this.llm.complete({
|
|
3339
|
+
messages,
|
|
3340
|
+
system: systemPrompt,
|
|
3341
|
+
tools: tools.length > 0 ? tools : void 0,
|
|
3342
|
+
maxTokens: 2048
|
|
3343
|
+
});
|
|
3344
|
+
totalInputTokens += response.usage.inputTokens;
|
|
3345
|
+
totalOutputTokens += response.usage.outputTokens;
|
|
3346
|
+
if (!response.toolCalls || response.toolCalls.length === 0 || iteration >= MAX_SUB_AGENT_ITERATIONS) {
|
|
3347
|
+
return {
|
|
3348
|
+
success: true,
|
|
3349
|
+
data: {
|
|
3350
|
+
response: response.content,
|
|
3351
|
+
iterations: iteration,
|
|
3352
|
+
usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens }
|
|
3353
|
+
},
|
|
3354
|
+
display: response.content
|
|
3355
|
+
};
|
|
3356
|
+
}
|
|
3357
|
+
iteration++;
|
|
3358
|
+
const assistantContent = [];
|
|
3359
|
+
if (response.content) {
|
|
3360
|
+
assistantContent.push({ type: "text", text: response.content });
|
|
3361
|
+
}
|
|
3362
|
+
for (const tc of response.toolCalls) {
|
|
3363
|
+
assistantContent.push({
|
|
3364
|
+
type: "tool_use",
|
|
3365
|
+
id: tc.id,
|
|
3366
|
+
name: tc.name,
|
|
3367
|
+
input: tc.input
|
|
3368
|
+
});
|
|
3369
|
+
}
|
|
3370
|
+
messages.push({ role: "assistant", content: assistantContent });
|
|
3371
|
+
const toolResultBlocks = [];
|
|
3372
|
+
for (const toolCall of response.toolCalls) {
|
|
3373
|
+
const result = await this.executeSubAgentTool(toolCall, context);
|
|
3374
|
+
toolResultBlocks.push({
|
|
3375
|
+
type: "tool_result",
|
|
3376
|
+
tool_use_id: toolCall.id,
|
|
3377
|
+
content: result.content,
|
|
3378
|
+
is_error: result.isError
|
|
3379
|
+
});
|
|
3380
|
+
}
|
|
3381
|
+
messages.push({ role: "user", content: toolResultBlocks });
|
|
3382
|
+
}
|
|
3487
3383
|
} catch (err) {
|
|
3488
3384
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
3489
3385
|
return {
|
|
@@ -3492,15 +3388,63 @@ Additional context: ${additionalContext}`;
|
|
|
3492
3388
|
};
|
|
3493
3389
|
}
|
|
3494
3390
|
}
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3391
|
+
buildSubAgentTools() {
|
|
3392
|
+
if (!this.skillRegistry)
|
|
3393
|
+
return [];
|
|
3394
|
+
return this.skillRegistry.getAll().filter((s) => s.metadata.name !== "delegate").map((s) => ({
|
|
3395
|
+
name: s.metadata.name,
|
|
3396
|
+
description: s.metadata.description,
|
|
3397
|
+
inputSchema: s.metadata.inputSchema
|
|
3398
|
+
}));
|
|
3399
|
+
}
|
|
3400
|
+
async executeSubAgentTool(toolCall, context) {
|
|
3401
|
+
const skill = this.skillRegistry?.get(toolCall.name);
|
|
3402
|
+
if (!skill) {
|
|
3403
|
+
return { content: `Error: Unknown tool "${toolCall.name}"`, isError: true };
|
|
3404
|
+
}
|
|
3405
|
+
if (this.securityManager) {
|
|
3406
|
+
const evaluation = this.securityManager.evaluate({
|
|
3407
|
+
userId: context.userId,
|
|
3408
|
+
action: toolCall.name,
|
|
3409
|
+
riskLevel: skill.metadata.riskLevel,
|
|
3410
|
+
platform: context.platform,
|
|
3411
|
+
chatId: context.chatId,
|
|
3412
|
+
chatType: context.chatType
|
|
3413
|
+
});
|
|
3414
|
+
if (!evaluation.allowed) {
|
|
3415
|
+
return {
|
|
3416
|
+
content: `Access denied: ${evaluation.reason}`,
|
|
3417
|
+
isError: true
|
|
3418
|
+
};
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
if (this.skillSandbox) {
|
|
3422
|
+
const result = await this.skillSandbox.execute(skill, toolCall.input, context);
|
|
3423
|
+
return {
|
|
3424
|
+
content: result.display ?? (result.success ? JSON.stringify(result.data) : result.error ?? "Unknown error"),
|
|
3425
|
+
isError: !result.success
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
try {
|
|
3429
|
+
const result = await skill.execute(toolCall.input, context);
|
|
3430
|
+
return {
|
|
3431
|
+
content: result.display ?? (result.success ? JSON.stringify(result.data) : result.error ?? "Unknown error"),
|
|
3432
|
+
isError: !result.success
|
|
3433
|
+
};
|
|
3434
|
+
} catch (error) {
|
|
3435
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3436
|
+
return { content: `Skill execution failed: ${msg}`, isError: true };
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
};
|
|
3440
|
+
}
|
|
3441
|
+
});
|
|
3442
|
+
|
|
3443
|
+
// ../skills/dist/built-in/email.js
|
|
3444
|
+
var EmailSkill;
|
|
3445
|
+
var init_email = __esm({
|
|
3446
|
+
"../skills/dist/built-in/email.js"() {
|
|
3447
|
+
"use strict";
|
|
3504
3448
|
init_skill();
|
|
3505
3449
|
EmailSkill = class extends Skill {
|
|
3506
3450
|
config;
|
|
@@ -3809,10 +3753,806 @@ Message ID: ${info.messageId}`
|
|
|
3809
3753
|
}
|
|
3810
3754
|
}
|
|
3811
3755
|
}
|
|
3812
|
-
return this.decodeBody(parts.slice(1).join("\n\n").slice(0, 5e3));
|
|
3813
|
-
}
|
|
3814
|
-
decodeBody(body) {
|
|
3815
|
-
return body.replace(/=\r?\n/g, "").replace(/=([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16))).trim();
|
|
3756
|
+
return this.decodeBody(parts.slice(1).join("\n\n").slice(0, 5e3));
|
|
3757
|
+
}
|
|
3758
|
+
decodeBody(body) {
|
|
3759
|
+
return body.replace(/=\r?\n/g, "").replace(/=([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16))).trim();
|
|
3760
|
+
}
|
|
3761
|
+
};
|
|
3762
|
+
}
|
|
3763
|
+
});
|
|
3764
|
+
|
|
3765
|
+
// ../skills/dist/built-in/http.js
|
|
3766
|
+
var MAX_RESPONSE_SIZE, HttpSkill;
|
|
3767
|
+
var init_http = __esm({
|
|
3768
|
+
"../skills/dist/built-in/http.js"() {
|
|
3769
|
+
"use strict";
|
|
3770
|
+
init_skill();
|
|
3771
|
+
MAX_RESPONSE_SIZE = 1e5;
|
|
3772
|
+
HttpSkill = class extends Skill {
|
|
3773
|
+
metadata = {
|
|
3774
|
+
name: "http",
|
|
3775
|
+
description: "Make HTTP requests to fetch web pages or call REST APIs. Use when you need to read a URL, call an API endpoint, or fetch data from the web. Supports GET, POST, PUT, PATCH, DELETE methods.",
|
|
3776
|
+
riskLevel: "write",
|
|
3777
|
+
version: "1.0.0",
|
|
3778
|
+
inputSchema: {
|
|
3779
|
+
type: "object",
|
|
3780
|
+
properties: {
|
|
3781
|
+
url: {
|
|
3782
|
+
type: "string",
|
|
3783
|
+
description: "The URL to request"
|
|
3784
|
+
},
|
|
3785
|
+
method: {
|
|
3786
|
+
type: "string",
|
|
3787
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
3788
|
+
description: "HTTP method (default: GET)"
|
|
3789
|
+
},
|
|
3790
|
+
headers: {
|
|
3791
|
+
type: "object",
|
|
3792
|
+
description: "Request headers as key-value pairs (optional)"
|
|
3793
|
+
},
|
|
3794
|
+
body: {
|
|
3795
|
+
type: "string",
|
|
3796
|
+
description: "Request body for POST/PUT/PATCH (optional)"
|
|
3797
|
+
}
|
|
3798
|
+
},
|
|
3799
|
+
required: ["url"]
|
|
3800
|
+
}
|
|
3801
|
+
};
|
|
3802
|
+
async execute(input2, _context) {
|
|
3803
|
+
const url = input2.url;
|
|
3804
|
+
const method = (input2.method ?? "GET").toUpperCase();
|
|
3805
|
+
const headers = input2.headers;
|
|
3806
|
+
const body = input2.body;
|
|
3807
|
+
if (!url || typeof url !== "string") {
|
|
3808
|
+
return { success: false, error: 'Missing required field "url"' };
|
|
3809
|
+
}
|
|
3810
|
+
try {
|
|
3811
|
+
new URL(url);
|
|
3812
|
+
} catch {
|
|
3813
|
+
return { success: false, error: `Invalid URL: "${url}"` };
|
|
3814
|
+
}
|
|
3815
|
+
try {
|
|
3816
|
+
const fetchOptions = {
|
|
3817
|
+
method,
|
|
3818
|
+
headers: {
|
|
3819
|
+
"User-Agent": "Alfred/1.0",
|
|
3820
|
+
...headers ?? {}
|
|
3821
|
+
},
|
|
3822
|
+
signal: AbortSignal.timeout(15e3)
|
|
3823
|
+
};
|
|
3824
|
+
if (body && ["POST", "PUT", "PATCH"].includes(method)) {
|
|
3825
|
+
fetchOptions.body = body;
|
|
3826
|
+
if (!headers?.["Content-Type"] && !headers?.["content-type"]) {
|
|
3827
|
+
fetchOptions.headers["Content-Type"] = "application/json";
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
const res = await fetch(url, fetchOptions);
|
|
3831
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
3832
|
+
const text = await res.text();
|
|
3833
|
+
const truncated = text.length > MAX_RESPONSE_SIZE;
|
|
3834
|
+
const responseBody = truncated ? text.slice(0, MAX_RESPONSE_SIZE) + "\n\n[... truncated]" : text;
|
|
3835
|
+
let display = responseBody;
|
|
3836
|
+
if (contentType.includes("text/html")) {
|
|
3837
|
+
display = this.stripHtml(responseBody).slice(0, 1e4);
|
|
3838
|
+
}
|
|
3839
|
+
const data = {
|
|
3840
|
+
status: res.status,
|
|
3841
|
+
statusText: res.statusText,
|
|
3842
|
+
contentType,
|
|
3843
|
+
bodyLength: text.length,
|
|
3844
|
+
truncated,
|
|
3845
|
+
body: responseBody
|
|
3846
|
+
};
|
|
3847
|
+
if (!res.ok) {
|
|
3848
|
+
return {
|
|
3849
|
+
success: true,
|
|
3850
|
+
data,
|
|
3851
|
+
display: `HTTP ${res.status} ${res.statusText}
|
|
3852
|
+
|
|
3853
|
+
${display.slice(0, 2e3)}`
|
|
3854
|
+
};
|
|
3855
|
+
}
|
|
3856
|
+
return {
|
|
3857
|
+
success: true,
|
|
3858
|
+
data,
|
|
3859
|
+
display: `HTTP ${res.status} OK (${text.length} bytes)
|
|
3860
|
+
|
|
3861
|
+
${display.slice(0, 5e3)}`
|
|
3862
|
+
};
|
|
3863
|
+
} catch (err) {
|
|
3864
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3865
|
+
return { success: false, error: `HTTP request failed: ${msg}` };
|
|
3866
|
+
}
|
|
3867
|
+
}
|
|
3868
|
+
stripHtml(html) {
|
|
3869
|
+
return html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, " ").replace(/\s+/g, " ").trim();
|
|
3870
|
+
}
|
|
3871
|
+
};
|
|
3872
|
+
}
|
|
3873
|
+
});
|
|
3874
|
+
|
|
3875
|
+
// ../skills/dist/built-in/file.js
|
|
3876
|
+
import fs4 from "node:fs";
|
|
3877
|
+
import path4 from "node:path";
|
|
3878
|
+
var MAX_READ_SIZE, FileSkill;
|
|
3879
|
+
var init_file = __esm({
|
|
3880
|
+
"../skills/dist/built-in/file.js"() {
|
|
3881
|
+
"use strict";
|
|
3882
|
+
init_skill();
|
|
3883
|
+
MAX_READ_SIZE = 5e5;
|
|
3884
|
+
FileSkill = class extends Skill {
|
|
3885
|
+
metadata = {
|
|
3886
|
+
name: "file",
|
|
3887
|
+
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.',
|
|
3888
|
+
riskLevel: "write",
|
|
3889
|
+
version: "2.0.0",
|
|
3890
|
+
inputSchema: {
|
|
3891
|
+
type: "object",
|
|
3892
|
+
properties: {
|
|
3893
|
+
action: {
|
|
3894
|
+
type: "string",
|
|
3895
|
+
enum: ["read", "write", "write_binary", "append", "list", "info", "exists", "move", "copy", "delete"],
|
|
3896
|
+
description: "The file operation to perform"
|
|
3897
|
+
},
|
|
3898
|
+
path: {
|
|
3899
|
+
type: "string",
|
|
3900
|
+
description: "Absolute or relative file/directory path (~ expands to home)"
|
|
3901
|
+
},
|
|
3902
|
+
destination: {
|
|
3903
|
+
type: "string",
|
|
3904
|
+
description: "Destination path for move/copy actions (~ expands to home)"
|
|
3905
|
+
},
|
|
3906
|
+
content: {
|
|
3907
|
+
type: "string",
|
|
3908
|
+
description: "Content to write (required for write/append; base64-encoded for write_binary)"
|
|
3909
|
+
}
|
|
3910
|
+
},
|
|
3911
|
+
required: ["action", "path"]
|
|
3912
|
+
}
|
|
3913
|
+
};
|
|
3914
|
+
async execute(input2, _context) {
|
|
3915
|
+
const action = input2.action;
|
|
3916
|
+
const rawPath = input2.path;
|
|
3917
|
+
const content = input2.content;
|
|
3918
|
+
const destination = input2.destination;
|
|
3919
|
+
if (!action || !rawPath) {
|
|
3920
|
+
return { success: false, error: 'Missing required fields "action" and "path"' };
|
|
3921
|
+
}
|
|
3922
|
+
const resolvedPath = this.resolvePath(rawPath);
|
|
3923
|
+
switch (action) {
|
|
3924
|
+
case "read":
|
|
3925
|
+
return this.readFile(resolvedPath);
|
|
3926
|
+
case "write":
|
|
3927
|
+
return this.writeFile(resolvedPath, content);
|
|
3928
|
+
case "write_binary":
|
|
3929
|
+
return this.writeBinaryFile(resolvedPath, content);
|
|
3930
|
+
case "append":
|
|
3931
|
+
return this.appendFile(resolvedPath, content);
|
|
3932
|
+
case "list":
|
|
3933
|
+
return this.listDir(resolvedPath);
|
|
3934
|
+
case "info":
|
|
3935
|
+
return this.fileInfo(resolvedPath);
|
|
3936
|
+
case "exists":
|
|
3937
|
+
return this.fileExists(resolvedPath);
|
|
3938
|
+
case "move":
|
|
3939
|
+
return this.moveFile(resolvedPath, destination);
|
|
3940
|
+
case "copy":
|
|
3941
|
+
return this.copyFile(resolvedPath, destination);
|
|
3942
|
+
case "delete":
|
|
3943
|
+
return this.deleteFile(resolvedPath);
|
|
3944
|
+
default:
|
|
3945
|
+
return { success: false, error: `Unknown action "${action}". Valid: read, write, write_binary, append, list, info, exists, move, copy, delete` };
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
resolvePath(raw) {
|
|
3949
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || "";
|
|
3950
|
+
const expanded = raw.startsWith("~") ? raw.replace("~", home) : raw;
|
|
3951
|
+
return path4.resolve(expanded);
|
|
3952
|
+
}
|
|
3953
|
+
readFile(filePath) {
|
|
3954
|
+
try {
|
|
3955
|
+
const stat = fs4.statSync(filePath);
|
|
3956
|
+
if (stat.isDirectory()) {
|
|
3957
|
+
return { success: false, error: `"${filePath}" is a directory, not a file. Use action "list" instead.` };
|
|
3958
|
+
}
|
|
3959
|
+
if (stat.size > MAX_READ_SIZE) {
|
|
3960
|
+
const content2 = fs4.readFileSync(filePath, "utf-8").slice(0, MAX_READ_SIZE);
|
|
3961
|
+
return {
|
|
3962
|
+
success: true,
|
|
3963
|
+
data: { path: filePath, size: stat.size, truncated: true },
|
|
3964
|
+
display: `${filePath} (${stat.size} bytes, truncated to ${MAX_READ_SIZE}):
|
|
3965
|
+
|
|
3966
|
+
${content2}`
|
|
3967
|
+
};
|
|
3968
|
+
}
|
|
3969
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
3970
|
+
return {
|
|
3971
|
+
success: true,
|
|
3972
|
+
data: { path: filePath, size: stat.size, content },
|
|
3973
|
+
display: content
|
|
3974
|
+
};
|
|
3975
|
+
} catch (err) {
|
|
3976
|
+
return { success: false, error: `Cannot read "${filePath}": ${err.message}` };
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
writeFile(filePath, content) {
|
|
3980
|
+
if (content === void 0 || content === null) {
|
|
3981
|
+
return { success: false, error: 'Missing "content" for write action' };
|
|
3982
|
+
}
|
|
3983
|
+
try {
|
|
3984
|
+
const dir = path4.dirname(filePath);
|
|
3985
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
3986
|
+
fs4.writeFileSync(filePath, content, "utf-8");
|
|
3987
|
+
return {
|
|
3988
|
+
success: true,
|
|
3989
|
+
data: { path: filePath, bytes: Buffer.byteLength(content) },
|
|
3990
|
+
display: `Written ${Buffer.byteLength(content)} bytes to ${filePath}`
|
|
3991
|
+
};
|
|
3992
|
+
} catch (err) {
|
|
3993
|
+
return { success: false, error: `Cannot write "${filePath}": ${err.message}` };
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
appendFile(filePath, content) {
|
|
3997
|
+
if (content === void 0 || content === null) {
|
|
3998
|
+
return { success: false, error: 'Missing "content" for append action' };
|
|
3999
|
+
}
|
|
4000
|
+
try {
|
|
4001
|
+
fs4.appendFileSync(filePath, content, "utf-8");
|
|
4002
|
+
return {
|
|
4003
|
+
success: true,
|
|
4004
|
+
data: { path: filePath, appendedBytes: Buffer.byteLength(content) },
|
|
4005
|
+
display: `Appended ${Buffer.byteLength(content)} bytes to ${filePath}`
|
|
4006
|
+
};
|
|
4007
|
+
} catch (err) {
|
|
4008
|
+
return { success: false, error: `Cannot append to "${filePath}": ${err.message}` };
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
listDir(dirPath) {
|
|
4012
|
+
try {
|
|
4013
|
+
const entries = fs4.readdirSync(dirPath, { withFileTypes: true });
|
|
4014
|
+
const items = entries.map((e) => ({
|
|
4015
|
+
name: e.name,
|
|
4016
|
+
type: e.isDirectory() ? "dir" : e.isSymbolicLink() ? "symlink" : "file"
|
|
4017
|
+
}));
|
|
4018
|
+
const display = items.length === 0 ? `${dirPath}: (empty)` : items.map((i) => `${i.type === "dir" ? "\u{1F4C1}" : "\u{1F4C4}"} ${i.name}`).join("\n");
|
|
4019
|
+
return { success: true, data: { path: dirPath, entries: items }, display };
|
|
4020
|
+
} catch (err) {
|
|
4021
|
+
return { success: false, error: `Cannot list "${dirPath}": ${err.message}` };
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
fileInfo(filePath) {
|
|
4025
|
+
try {
|
|
4026
|
+
const stat = fs4.statSync(filePath);
|
|
4027
|
+
const info = {
|
|
4028
|
+
path: filePath,
|
|
4029
|
+
type: stat.isDirectory() ? "directory" : stat.isFile() ? "file" : "other",
|
|
4030
|
+
size: stat.size,
|
|
4031
|
+
created: stat.birthtime.toISOString(),
|
|
4032
|
+
modified: stat.mtime.toISOString(),
|
|
4033
|
+
permissions: stat.mode.toString(8)
|
|
4034
|
+
};
|
|
4035
|
+
return {
|
|
4036
|
+
success: true,
|
|
4037
|
+
data: info,
|
|
4038
|
+
display: `${info.type}: ${filePath}
|
|
4039
|
+
Size: ${stat.size} bytes
|
|
4040
|
+
Modified: ${info.modified}`
|
|
4041
|
+
};
|
|
4042
|
+
} catch (err) {
|
|
4043
|
+
return { success: false, error: `Cannot stat "${filePath}": ${err.message}` };
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
fileExists(filePath) {
|
|
4047
|
+
const exists = fs4.existsSync(filePath);
|
|
4048
|
+
return {
|
|
4049
|
+
success: true,
|
|
4050
|
+
data: { path: filePath, exists },
|
|
4051
|
+
display: exists ? `Yes, "${filePath}" exists` : `No, "${filePath}" does not exist`
|
|
4052
|
+
};
|
|
4053
|
+
}
|
|
4054
|
+
writeBinaryFile(filePath, base64Content) {
|
|
4055
|
+
if (!base64Content) {
|
|
4056
|
+
return { success: false, error: 'Missing "content" (base64-encoded) for write_binary action' };
|
|
4057
|
+
}
|
|
4058
|
+
try {
|
|
4059
|
+
const dir = path4.dirname(filePath);
|
|
4060
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
4061
|
+
const buffer = Buffer.from(base64Content, "base64");
|
|
4062
|
+
fs4.writeFileSync(filePath, buffer);
|
|
4063
|
+
return {
|
|
4064
|
+
success: true,
|
|
4065
|
+
data: { path: filePath, bytes: buffer.length },
|
|
4066
|
+
display: `Written ${buffer.length} bytes (binary) to ${filePath}`
|
|
4067
|
+
};
|
|
4068
|
+
} catch (err) {
|
|
4069
|
+
return { success: false, error: `Cannot write "${filePath}": ${err.message}` };
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4072
|
+
moveFile(source, destination) {
|
|
4073
|
+
if (!destination) {
|
|
4074
|
+
return { success: false, error: 'Missing "destination" for move action' };
|
|
4075
|
+
}
|
|
4076
|
+
const resolvedDest = this.resolvePath(destination);
|
|
4077
|
+
try {
|
|
4078
|
+
const destDir = path4.dirname(resolvedDest);
|
|
4079
|
+
fs4.mkdirSync(destDir, { recursive: true });
|
|
4080
|
+
fs4.renameSync(source, resolvedDest);
|
|
4081
|
+
return {
|
|
4082
|
+
success: true,
|
|
4083
|
+
data: { from: source, to: resolvedDest },
|
|
4084
|
+
display: `Moved ${source} \u2192 ${resolvedDest}`
|
|
4085
|
+
};
|
|
4086
|
+
} catch (err) {
|
|
4087
|
+
try {
|
|
4088
|
+
fs4.copyFileSync(source, resolvedDest);
|
|
4089
|
+
fs4.unlinkSync(source);
|
|
4090
|
+
return {
|
|
4091
|
+
success: true,
|
|
4092
|
+
data: { from: source, to: resolvedDest },
|
|
4093
|
+
display: `Moved ${source} \u2192 ${resolvedDest}`
|
|
4094
|
+
};
|
|
4095
|
+
} catch (err2) {
|
|
4096
|
+
return { success: false, error: `Cannot move "${source}" to "${resolvedDest}": ${err2.message}` };
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
copyFile(source, destination) {
|
|
4101
|
+
if (!destination) {
|
|
4102
|
+
return { success: false, error: 'Missing "destination" for copy action' };
|
|
4103
|
+
}
|
|
4104
|
+
const resolvedDest = this.resolvePath(destination);
|
|
4105
|
+
try {
|
|
4106
|
+
const destDir = path4.dirname(resolvedDest);
|
|
4107
|
+
fs4.mkdirSync(destDir, { recursive: true });
|
|
4108
|
+
fs4.copyFileSync(source, resolvedDest);
|
|
4109
|
+
return {
|
|
4110
|
+
success: true,
|
|
4111
|
+
data: { from: source, to: resolvedDest },
|
|
4112
|
+
display: `Copied ${source} \u2192 ${resolvedDest}`
|
|
4113
|
+
};
|
|
4114
|
+
} catch (err) {
|
|
4115
|
+
return { success: false, error: `Cannot copy "${source}" to "${resolvedDest}": ${err.message}` };
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
deleteFile(filePath) {
|
|
4119
|
+
try {
|
|
4120
|
+
if (!fs4.existsSync(filePath)) {
|
|
4121
|
+
return { success: false, error: `"${filePath}" does not exist` };
|
|
4122
|
+
}
|
|
4123
|
+
const stat = fs4.statSync(filePath);
|
|
4124
|
+
if (stat.isDirectory()) {
|
|
4125
|
+
return { success: false, error: `"${filePath}" is a directory. Use shell for directory deletion.` };
|
|
4126
|
+
}
|
|
4127
|
+
fs4.unlinkSync(filePath);
|
|
4128
|
+
return {
|
|
4129
|
+
success: true,
|
|
4130
|
+
data: { path: filePath },
|
|
4131
|
+
display: `Deleted ${filePath}`
|
|
4132
|
+
};
|
|
4133
|
+
} catch (err) {
|
|
4134
|
+
return { success: false, error: `Cannot delete "${filePath}": ${err.message}` };
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
};
|
|
4138
|
+
}
|
|
4139
|
+
});
|
|
4140
|
+
|
|
4141
|
+
// ../skills/dist/built-in/clipboard.js
|
|
4142
|
+
import { execSync } from "node:child_process";
|
|
4143
|
+
var ClipboardSkill;
|
|
4144
|
+
var init_clipboard = __esm({
|
|
4145
|
+
"../skills/dist/built-in/clipboard.js"() {
|
|
4146
|
+
"use strict";
|
|
4147
|
+
init_skill();
|
|
4148
|
+
ClipboardSkill = class extends Skill {
|
|
4149
|
+
metadata = {
|
|
4150
|
+
name: "clipboard",
|
|
4151
|
+
description: "Read or write the system clipboard. Use when the user asks to copy something, paste from clipboard, or check what is in their clipboard.",
|
|
4152
|
+
riskLevel: "write",
|
|
4153
|
+
version: "1.0.0",
|
|
4154
|
+
inputSchema: {
|
|
4155
|
+
type: "object",
|
|
4156
|
+
properties: {
|
|
4157
|
+
action: {
|
|
4158
|
+
type: "string",
|
|
4159
|
+
enum: ["read", "write"],
|
|
4160
|
+
description: '"read" to get clipboard contents, "write" to set clipboard contents'
|
|
4161
|
+
},
|
|
4162
|
+
text: {
|
|
4163
|
+
type: "string",
|
|
4164
|
+
description: "Text to copy to clipboard (required for write)"
|
|
4165
|
+
}
|
|
4166
|
+
},
|
|
4167
|
+
required: ["action"]
|
|
4168
|
+
}
|
|
4169
|
+
};
|
|
4170
|
+
async execute(input2, _context) {
|
|
4171
|
+
const action = input2.action;
|
|
4172
|
+
switch (action) {
|
|
4173
|
+
case "read":
|
|
4174
|
+
return this.readClipboard();
|
|
4175
|
+
case "write":
|
|
4176
|
+
return this.writeClipboard(input2.text);
|
|
4177
|
+
default:
|
|
4178
|
+
return { success: false, error: `Unknown action "${action}". Valid: read, write` };
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
readClipboard() {
|
|
4182
|
+
try {
|
|
4183
|
+
let content;
|
|
4184
|
+
switch (process.platform) {
|
|
4185
|
+
case "darwin":
|
|
4186
|
+
content = execSync("pbpaste", { encoding: "utf-8", timeout: 5e3 });
|
|
4187
|
+
break;
|
|
4188
|
+
case "win32":
|
|
4189
|
+
content = execSync("powershell -NoProfile -Command Get-Clipboard", {
|
|
4190
|
+
encoding: "utf-8",
|
|
4191
|
+
timeout: 5e3
|
|
4192
|
+
}).replace(/\r\n$/, "");
|
|
4193
|
+
break;
|
|
4194
|
+
default:
|
|
4195
|
+
content = execSync("xclip -selection clipboard -o 2>/dev/null || xsel --clipboard --output", {
|
|
4196
|
+
encoding: "utf-8",
|
|
4197
|
+
timeout: 5e3
|
|
4198
|
+
});
|
|
4199
|
+
break;
|
|
4200
|
+
}
|
|
4201
|
+
if (!content || content.trim().length === 0) {
|
|
4202
|
+
return { success: true, data: { content: "" }, display: "Clipboard is empty." };
|
|
4203
|
+
}
|
|
4204
|
+
return {
|
|
4205
|
+
success: true,
|
|
4206
|
+
data: { content },
|
|
4207
|
+
display: content.length > 2e3 ? content.slice(0, 2e3) + "\n\n[... truncated]" : content
|
|
4208
|
+
};
|
|
4209
|
+
} catch (err) {
|
|
4210
|
+
return { success: false, error: `Failed to read clipboard: ${err.message}` };
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
writeClipboard(text) {
|
|
4214
|
+
if (!text || typeof text !== "string") {
|
|
4215
|
+
return { success: false, error: 'Missing "text" for write action' };
|
|
4216
|
+
}
|
|
4217
|
+
try {
|
|
4218
|
+
switch (process.platform) {
|
|
4219
|
+
case "darwin":
|
|
4220
|
+
execSync("pbcopy", { input: text, timeout: 5e3 });
|
|
4221
|
+
break;
|
|
4222
|
+
case "win32":
|
|
4223
|
+
execSync('powershell -NoProfile -Command "$input | Set-Clipboard"', {
|
|
4224
|
+
input: text,
|
|
4225
|
+
timeout: 5e3
|
|
4226
|
+
});
|
|
4227
|
+
break;
|
|
4228
|
+
default:
|
|
4229
|
+
execSync("xclip -selection clipboard 2>/dev/null || xsel --clipboard --input", {
|
|
4230
|
+
input: text,
|
|
4231
|
+
timeout: 5e3
|
|
4232
|
+
});
|
|
4233
|
+
break;
|
|
4234
|
+
}
|
|
4235
|
+
return {
|
|
4236
|
+
success: true,
|
|
4237
|
+
data: { copiedLength: text.length },
|
|
4238
|
+
display: `Copied ${text.length} characters to clipboard.`
|
|
4239
|
+
};
|
|
4240
|
+
} catch (err) {
|
|
4241
|
+
return { success: false, error: `Failed to write clipboard: ${err.message}` };
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
};
|
|
4245
|
+
}
|
|
4246
|
+
});
|
|
4247
|
+
|
|
4248
|
+
// ../skills/dist/built-in/screenshot.js
|
|
4249
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
4250
|
+
import path5 from "node:path";
|
|
4251
|
+
import os from "node:os";
|
|
4252
|
+
var ScreenshotSkill;
|
|
4253
|
+
var init_screenshot = __esm({
|
|
4254
|
+
"../skills/dist/built-in/screenshot.js"() {
|
|
4255
|
+
"use strict";
|
|
4256
|
+
init_skill();
|
|
4257
|
+
ScreenshotSkill = class extends Skill {
|
|
4258
|
+
metadata = {
|
|
4259
|
+
name: "screenshot",
|
|
4260
|
+
description: "Take a screenshot of the current screen and save it to a file. Use when the user asks to capture their screen or take a screenshot.",
|
|
4261
|
+
riskLevel: "write",
|
|
4262
|
+
version: "1.0.0",
|
|
4263
|
+
inputSchema: {
|
|
4264
|
+
type: "object",
|
|
4265
|
+
properties: {
|
|
4266
|
+
path: {
|
|
4267
|
+
type: "string",
|
|
4268
|
+
description: "Output file path (optional, defaults to ~/Desktop/screenshot-<timestamp>.png)"
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
};
|
|
4273
|
+
async execute(input2, _context) {
|
|
4274
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
4275
|
+
const defaultDir = path5.join(os.homedir(), "Desktop");
|
|
4276
|
+
const outputPath = input2.path || path5.join(defaultDir, `screenshot-${timestamp}.png`);
|
|
4277
|
+
try {
|
|
4278
|
+
switch (process.platform) {
|
|
4279
|
+
case "darwin":
|
|
4280
|
+
execSync2(`screencapture -x "${outputPath}"`, { timeout: 1e4 });
|
|
4281
|
+
break;
|
|
4282
|
+
case "win32":
|
|
4283
|
+
execSync2(`powershell -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds; $bitmap = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height); $graphics = [System.Drawing.Graphics]::FromImage($bitmap); $graphics.CopyFromScreen($screen.Location, [System.Drawing.Point]::Empty, $screen.Size); $bitmap.Save('${outputPath.replace(/'/g, "''")}'); $graphics.Dispose(); $bitmap.Dispose()"`, { timeout: 1e4 });
|
|
4284
|
+
break;
|
|
4285
|
+
default:
|
|
4286
|
+
try {
|
|
4287
|
+
execSync2(`scrot "${outputPath}"`, { timeout: 1e4 });
|
|
4288
|
+
} catch {
|
|
4289
|
+
try {
|
|
4290
|
+
execSync2(`import -window root "${outputPath}"`, { timeout: 1e4 });
|
|
4291
|
+
} catch {
|
|
4292
|
+
execSync2(`gnome-screenshot -f "${outputPath}"`, { timeout: 1e4 });
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
break;
|
|
4296
|
+
}
|
|
4297
|
+
return {
|
|
4298
|
+
success: true,
|
|
4299
|
+
data: { path: outputPath },
|
|
4300
|
+
display: `Screenshot saved to ${outputPath}`
|
|
4301
|
+
};
|
|
4302
|
+
} catch (err) {
|
|
4303
|
+
return { success: false, error: `Screenshot failed: ${err.message}` };
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
};
|
|
4307
|
+
}
|
|
4308
|
+
});
|
|
4309
|
+
|
|
4310
|
+
// ../skills/dist/built-in/browser.js
|
|
4311
|
+
import path6 from "node:path";
|
|
4312
|
+
import os2 from "node:os";
|
|
4313
|
+
var MAX_TEXT_LENGTH, BrowserSkill;
|
|
4314
|
+
var init_browser = __esm({
|
|
4315
|
+
"../skills/dist/built-in/browser.js"() {
|
|
4316
|
+
"use strict";
|
|
4317
|
+
init_skill();
|
|
4318
|
+
MAX_TEXT_LENGTH = 5e4;
|
|
4319
|
+
BrowserSkill = class extends Skill {
|
|
4320
|
+
browser = null;
|
|
4321
|
+
page = null;
|
|
4322
|
+
metadata = {
|
|
4323
|
+
name: "browser",
|
|
4324
|
+
description: "Open web pages in a real browser (Puppeteer/Chromium). Renders JavaScript, so it works with SPAs and dynamic sites. Can also interact with pages: click buttons, fill forms, take screenshots. Use when http skill returns empty/broken content, or when you need to interact with a web page.",
|
|
4325
|
+
riskLevel: "write",
|
|
4326
|
+
version: "1.0.0",
|
|
4327
|
+
inputSchema: {
|
|
4328
|
+
type: "object",
|
|
4329
|
+
properties: {
|
|
4330
|
+
action: {
|
|
4331
|
+
type: "string",
|
|
4332
|
+
enum: ["open", "screenshot", "click", "type", "evaluate", "close"],
|
|
4333
|
+
description: "open = navigate to URL and return page text. screenshot = save screenshot of current page. click = click element by CSS selector. type = type text into input by CSS selector. evaluate = run JavaScript on the page. close = close the browser."
|
|
4334
|
+
},
|
|
4335
|
+
url: {
|
|
4336
|
+
type: "string",
|
|
4337
|
+
description: 'URL to open (required for "open", optional for "screenshot")'
|
|
4338
|
+
},
|
|
4339
|
+
selector: {
|
|
4340
|
+
type: "string",
|
|
4341
|
+
description: 'CSS selector for the element (required for "click" and "type")'
|
|
4342
|
+
},
|
|
4343
|
+
text: {
|
|
4344
|
+
type: "string",
|
|
4345
|
+
description: 'Text to type (required for "type")'
|
|
4346
|
+
},
|
|
4347
|
+
script: {
|
|
4348
|
+
type: "string",
|
|
4349
|
+
description: 'JavaScript code to evaluate (required for "evaluate")'
|
|
4350
|
+
},
|
|
4351
|
+
path: {
|
|
4352
|
+
type: "string",
|
|
4353
|
+
description: "File path to save screenshot (optional, defaults to Desktop)"
|
|
4354
|
+
}
|
|
4355
|
+
},
|
|
4356
|
+
required: ["action"]
|
|
4357
|
+
}
|
|
4358
|
+
};
|
|
4359
|
+
async execute(input2, _context) {
|
|
4360
|
+
const action = input2.action;
|
|
4361
|
+
if (action === "close") {
|
|
4362
|
+
return this.closeBrowser();
|
|
4363
|
+
}
|
|
4364
|
+
const pup = await this.loadPuppeteer();
|
|
4365
|
+
if (!pup) {
|
|
4366
|
+
return {
|
|
4367
|
+
success: false,
|
|
4368
|
+
error: "Puppeteer is not installed. Run: npm install -g puppeteer\nOr add it to Alfred: npm install puppeteer"
|
|
4369
|
+
};
|
|
4370
|
+
}
|
|
4371
|
+
switch (action) {
|
|
4372
|
+
case "open":
|
|
4373
|
+
return this.openPage(pup, input2);
|
|
4374
|
+
case "screenshot":
|
|
4375
|
+
return this.screenshotPage(pup, input2);
|
|
4376
|
+
case "click":
|
|
4377
|
+
return this.clickElement(input2);
|
|
4378
|
+
case "type":
|
|
4379
|
+
return this.typeText(input2);
|
|
4380
|
+
case "evaluate":
|
|
4381
|
+
return this.evaluateScript(input2);
|
|
4382
|
+
default:
|
|
4383
|
+
return { success: false, error: `Unknown action "${action}". Valid: open, screenshot, click, type, evaluate, close` };
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4386
|
+
async loadPuppeteer() {
|
|
4387
|
+
try {
|
|
4388
|
+
const mod = await Function('return import("puppeteer")')();
|
|
4389
|
+
return this.resolvePuppeteerModule(mod);
|
|
4390
|
+
} catch {
|
|
4391
|
+
try {
|
|
4392
|
+
const mod = await Function('return import("puppeteer-core")')();
|
|
4393
|
+
return this.resolvePuppeteerModule(mod);
|
|
4394
|
+
} catch {
|
|
4395
|
+
return null;
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
resolvePuppeteerModule(mod) {
|
|
4400
|
+
const m = mod;
|
|
4401
|
+
if (typeof m.launch === "function")
|
|
4402
|
+
return m;
|
|
4403
|
+
const def = m.default;
|
|
4404
|
+
return def;
|
|
4405
|
+
}
|
|
4406
|
+
async ensureBrowser(pup) {
|
|
4407
|
+
if (this.browser && this.browser.connected) {
|
|
4408
|
+
return this.browser;
|
|
4409
|
+
}
|
|
4410
|
+
this.browser = await pup.launch({
|
|
4411
|
+
headless: true,
|
|
4412
|
+
args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"]
|
|
4413
|
+
});
|
|
4414
|
+
return this.browser;
|
|
4415
|
+
}
|
|
4416
|
+
async ensurePage(pup) {
|
|
4417
|
+
const browser = await this.ensureBrowser(pup);
|
|
4418
|
+
if (!this.page) {
|
|
4419
|
+
this.page = await browser.newPage();
|
|
4420
|
+
await this.page.setViewport({ width: 1280, height: 900 });
|
|
4421
|
+
}
|
|
4422
|
+
return this.page;
|
|
4423
|
+
}
|
|
4424
|
+
async openPage(pup, input2) {
|
|
4425
|
+
const url = input2.url;
|
|
4426
|
+
if (!url) {
|
|
4427
|
+
return { success: false, error: 'Missing "url" for open action' };
|
|
4428
|
+
}
|
|
4429
|
+
try {
|
|
4430
|
+
const page = await this.ensurePage(pup);
|
|
4431
|
+
await page.goto(url, { waitUntil: "networkidle2", timeout: 3e4 });
|
|
4432
|
+
const title = await page.title();
|
|
4433
|
+
const text = await page.evaluate(`
|
|
4434
|
+
(() => {
|
|
4435
|
+
document.querySelectorAll('script, style, noscript').forEach(el => el.remove());
|
|
4436
|
+
return document.body?.innerText ?? '';
|
|
4437
|
+
})()
|
|
4438
|
+
`);
|
|
4439
|
+
const trimmed = text.length > MAX_TEXT_LENGTH ? text.slice(0, MAX_TEXT_LENGTH) + "\n\n[... truncated]" : text;
|
|
4440
|
+
const cleaned = trimmed.replace(/\n{3,}/g, "\n\n").trim();
|
|
4441
|
+
return {
|
|
4442
|
+
success: true,
|
|
4443
|
+
data: { url: page.url(), title, length: text.length },
|
|
4444
|
+
display: `**${title}** (${page.url()})
|
|
4445
|
+
|
|
4446
|
+
${cleaned}`
|
|
4447
|
+
};
|
|
4448
|
+
} catch (err) {
|
|
4449
|
+
return { success: false, error: `Failed to open "${url}": ${err.message}` };
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
async screenshotPage(pup, input2) {
|
|
4453
|
+
try {
|
|
4454
|
+
const page = await this.ensurePage(pup);
|
|
4455
|
+
const url = input2.url;
|
|
4456
|
+
if (url) {
|
|
4457
|
+
await page.goto(url, { waitUntil: "networkidle2", timeout: 3e4 });
|
|
4458
|
+
}
|
|
4459
|
+
const currentUrl = page.url();
|
|
4460
|
+
if (currentUrl === "about:blank") {
|
|
4461
|
+
return { success: false, error: 'No page is open. Use action "open" with a URL first, or provide a URL.' };
|
|
4462
|
+
}
|
|
4463
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
4464
|
+
const outputPath = input2.path || path6.join(os2.homedir(), "Desktop", `browser-${timestamp}.png`);
|
|
4465
|
+
await page.screenshot({ path: outputPath, fullPage: false });
|
|
4466
|
+
return {
|
|
4467
|
+
success: true,
|
|
4468
|
+
data: { path: outputPath, url: currentUrl },
|
|
4469
|
+
display: `Screenshot saved to ${outputPath}`
|
|
4470
|
+
};
|
|
4471
|
+
} catch (err) {
|
|
4472
|
+
return { success: false, error: `Screenshot failed: ${err.message}` };
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
async clickElement(input2) {
|
|
4476
|
+
const selector = input2.selector;
|
|
4477
|
+
if (!selector) {
|
|
4478
|
+
return { success: false, error: 'Missing "selector" for click action' };
|
|
4479
|
+
}
|
|
4480
|
+
if (!this.page) {
|
|
4481
|
+
return { success: false, error: 'No page is open. Use action "open" first.' };
|
|
4482
|
+
}
|
|
4483
|
+
try {
|
|
4484
|
+
await this.page.waitForSelector(selector, { timeout: 5e3 });
|
|
4485
|
+
await this.page.click(selector);
|
|
4486
|
+
try {
|
|
4487
|
+
await this.page.waitForNavigation({ timeout: 3e3 });
|
|
4488
|
+
} catch {
|
|
4489
|
+
}
|
|
4490
|
+
const title = await this.page.title();
|
|
4491
|
+
return {
|
|
4492
|
+
success: true,
|
|
4493
|
+
data: { selector, url: this.page.url(), title },
|
|
4494
|
+
display: `Clicked "${selector}" \u2014 now on: ${title} (${this.page.url()})`
|
|
4495
|
+
};
|
|
4496
|
+
} catch (err) {
|
|
4497
|
+
return { success: false, error: `Click failed on "${selector}": ${err.message}` };
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
async typeText(input2) {
|
|
4501
|
+
const selector = input2.selector;
|
|
4502
|
+
const text = input2.text;
|
|
4503
|
+
if (!selector)
|
|
4504
|
+
return { success: false, error: 'Missing "selector" for type action' };
|
|
4505
|
+
if (!text)
|
|
4506
|
+
return { success: false, error: 'Missing "text" for type action' };
|
|
4507
|
+
if (!this.page) {
|
|
4508
|
+
return { success: false, error: 'No page is open. Use action "open" first.' };
|
|
4509
|
+
}
|
|
4510
|
+
try {
|
|
4511
|
+
await this.page.waitForSelector(selector, { timeout: 5e3 });
|
|
4512
|
+
await this.page.click(selector);
|
|
4513
|
+
await this.page.type(selector, text, { delay: 50 });
|
|
4514
|
+
return {
|
|
4515
|
+
success: true,
|
|
4516
|
+
data: { selector, textLength: text.length },
|
|
4517
|
+
display: `Typed ${text.length} characters into "${selector}"`
|
|
4518
|
+
};
|
|
4519
|
+
} catch (err) {
|
|
4520
|
+
return { success: false, error: `Type failed on "${selector}": ${err.message}` };
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
async evaluateScript(input2) {
|
|
4524
|
+
const script = input2.script;
|
|
4525
|
+
if (!script) {
|
|
4526
|
+
return { success: false, error: 'Missing "script" for evaluate action' };
|
|
4527
|
+
}
|
|
4528
|
+
if (!this.page) {
|
|
4529
|
+
return { success: false, error: 'No page is open. Use action "open" first.' };
|
|
4530
|
+
}
|
|
4531
|
+
try {
|
|
4532
|
+
const result = await this.page.evaluate(script);
|
|
4533
|
+
const output2 = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
4534
|
+
return {
|
|
4535
|
+
success: true,
|
|
4536
|
+
data: { result },
|
|
4537
|
+
display: output2?.slice(0, 1e4) ?? "(no output)"
|
|
4538
|
+
};
|
|
4539
|
+
} catch (err) {
|
|
4540
|
+
return { success: false, error: `Evaluate failed: ${err.message}` };
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
async closeBrowser() {
|
|
4544
|
+
try {
|
|
4545
|
+
this.page = null;
|
|
4546
|
+
if (this.browser) {
|
|
4547
|
+
await this.browser.close();
|
|
4548
|
+
this.browser = null;
|
|
4549
|
+
}
|
|
4550
|
+
return { success: true, display: "Browser closed." };
|
|
4551
|
+
} catch (err) {
|
|
4552
|
+
this.browser = null;
|
|
4553
|
+
this.page = null;
|
|
4554
|
+
return { success: false, error: `Close failed: ${err.message}` };
|
|
4555
|
+
}
|
|
3816
4556
|
}
|
|
3817
4557
|
};
|
|
3818
4558
|
}
|
|
@@ -3831,13 +4571,16 @@ var init_dist6 = __esm({
|
|
|
3831
4571
|
init_web_search();
|
|
3832
4572
|
init_reminder();
|
|
3833
4573
|
init_note();
|
|
3834
|
-
init_summarize();
|
|
3835
|
-
init_translate();
|
|
3836
4574
|
init_weather();
|
|
3837
4575
|
init_shell();
|
|
3838
4576
|
init_memory();
|
|
3839
4577
|
init_delegate();
|
|
3840
4578
|
init_email();
|
|
4579
|
+
init_http();
|
|
4580
|
+
init_file();
|
|
4581
|
+
init_clipboard();
|
|
4582
|
+
init_screenshot();
|
|
4583
|
+
init_browser();
|
|
3841
4584
|
}
|
|
3842
4585
|
});
|
|
3843
4586
|
|
|
@@ -3870,13 +4613,16 @@ var init_conversation_manager = __esm({
|
|
|
3870
4613
|
});
|
|
3871
4614
|
|
|
3872
4615
|
// ../core/dist/message-pipeline.js
|
|
3873
|
-
|
|
4616
|
+
import fs5 from "node:fs";
|
|
4617
|
+
import path7 from "node:path";
|
|
4618
|
+
var MAX_TOOL_ITERATIONS, TOKEN_BUDGET_RATIO, MAX_INLINE_FILE_SIZE, MessagePipeline;
|
|
3874
4619
|
var init_message_pipeline = __esm({
|
|
3875
4620
|
"../core/dist/message-pipeline.js"() {
|
|
3876
4621
|
"use strict";
|
|
3877
4622
|
init_dist4();
|
|
3878
4623
|
MAX_TOOL_ITERATIONS = 10;
|
|
3879
4624
|
TOKEN_BUDGET_RATIO = 0.85;
|
|
4625
|
+
MAX_INLINE_FILE_SIZE = 1e5;
|
|
3880
4626
|
MessagePipeline = class {
|
|
3881
4627
|
llm;
|
|
3882
4628
|
conversationManager;
|
|
@@ -3886,8 +4632,10 @@ var init_message_pipeline = __esm({
|
|
|
3886
4632
|
skillSandbox;
|
|
3887
4633
|
securityManager;
|
|
3888
4634
|
memoryRepo;
|
|
4635
|
+
speechTranscriber;
|
|
4636
|
+
inboxPath;
|
|
3889
4637
|
promptBuilder;
|
|
3890
|
-
constructor(llm, conversationManager, users, logger, skillRegistry, skillSandbox, securityManager, memoryRepo) {
|
|
4638
|
+
constructor(llm, conversationManager, users, logger, skillRegistry, skillSandbox, securityManager, memoryRepo, speechTranscriber, inboxPath) {
|
|
3891
4639
|
this.llm = llm;
|
|
3892
4640
|
this.conversationManager = conversationManager;
|
|
3893
4641
|
this.users = users;
|
|
@@ -3896,6 +4644,8 @@ var init_message_pipeline = __esm({
|
|
|
3896
4644
|
this.skillSandbox = skillSandbox;
|
|
3897
4645
|
this.securityManager = securityManager;
|
|
3898
4646
|
this.memoryRepo = memoryRepo;
|
|
4647
|
+
this.speechTranscriber = speechTranscriber;
|
|
4648
|
+
this.inboxPath = inboxPath;
|
|
3899
4649
|
this.promptBuilder = new PromptBuilder();
|
|
3900
4650
|
}
|
|
3901
4651
|
async process(message, onProgress) {
|
|
@@ -3917,7 +4667,8 @@ var init_message_pipeline = __esm({
|
|
|
3917
4667
|
const tools = skillMetas ? this.promptBuilder.buildTools(skillMetas) : void 0;
|
|
3918
4668
|
const system = this.promptBuilder.buildSystemPrompt(memories, skillMetas);
|
|
3919
4669
|
const allMessages = this.promptBuilder.buildMessages(history);
|
|
3920
|
-
|
|
4670
|
+
const userContent = await this.buildUserContent(message, onProgress);
|
|
4671
|
+
allMessages.push({ role: "user", content: userContent });
|
|
3921
4672
|
const messages = this.trimToContextWindow(system, allMessages);
|
|
3922
4673
|
let response;
|
|
3923
4674
|
let iteration = 0;
|
|
@@ -4048,6 +4799,20 @@ var init_message_pipeline = __esm({
|
|
|
4048
4799
|
return `Getting system info...`;
|
|
4049
4800
|
case "delegate":
|
|
4050
4801
|
return `Delegating sub-task...`;
|
|
4802
|
+
case "http":
|
|
4803
|
+
return `Fetching: ${String(input2.url ?? "").slice(0, 60)}`;
|
|
4804
|
+
case "file":
|
|
4805
|
+
return `File: ${String(input2.action ?? "")} ${String(input2.path ?? "").slice(0, 50)}`;
|
|
4806
|
+
case "clipboard":
|
|
4807
|
+
return `Clipboard: ${String(input2.action ?? "")}`;
|
|
4808
|
+
case "screenshot":
|
|
4809
|
+
return `Taking screenshot...`;
|
|
4810
|
+
case "browser":
|
|
4811
|
+
return `Browser: ${String(input2.action ?? "")} ${String(input2.url ?? "").slice(0, 50)}`;
|
|
4812
|
+
case "weather":
|
|
4813
|
+
return `Weather: ${String(input2.location ?? "")}`;
|
|
4814
|
+
case "note":
|
|
4815
|
+
return `Note: ${String(input2.action ?? "")}`;
|
|
4051
4816
|
default:
|
|
4052
4817
|
return `Using ${toolName}...`;
|
|
4053
4818
|
}
|
|
@@ -4089,6 +4854,135 @@ var init_message_pipeline = __esm({
|
|
|
4089
4854
|
keptMessages.push(latestMsg);
|
|
4090
4855
|
return keptMessages;
|
|
4091
4856
|
}
|
|
4857
|
+
/**
|
|
4858
|
+
* Build the user content for the LLM request.
|
|
4859
|
+
* Handles images (as vision blocks), audio (transcribed via Whisper),
|
|
4860
|
+
* documents/files (saved to inbox), and plain text.
|
|
4861
|
+
*/
|
|
4862
|
+
async buildUserContent(message, onProgress) {
|
|
4863
|
+
const attachments = message.attachments?.filter((a) => a.data) ?? [];
|
|
4864
|
+
if (attachments.length === 0) {
|
|
4865
|
+
return message.text;
|
|
4866
|
+
}
|
|
4867
|
+
const blocks = [];
|
|
4868
|
+
for (const attachment of attachments) {
|
|
4869
|
+
if (attachment.type === "image" && attachment.data) {
|
|
4870
|
+
blocks.push({
|
|
4871
|
+
type: "image",
|
|
4872
|
+
source: {
|
|
4873
|
+
type: "base64",
|
|
4874
|
+
media_type: attachment.mimeType ?? "image/jpeg",
|
|
4875
|
+
data: attachment.data.toString("base64")
|
|
4876
|
+
}
|
|
4877
|
+
});
|
|
4878
|
+
this.logger.info({ mimeType: attachment.mimeType, size: attachment.size }, "Image attached to LLM request");
|
|
4879
|
+
} else if (attachment.type === "audio" && attachment.data) {
|
|
4880
|
+
if (this.speechTranscriber) {
|
|
4881
|
+
onProgress?.("Transcribing voice...");
|
|
4882
|
+
try {
|
|
4883
|
+
const transcript = await this.speechTranscriber.transcribe(attachment.data, attachment.mimeType ?? "audio/ogg");
|
|
4884
|
+
const label = message.text === "[Voice message]" ? "" : `${message.text}
|
|
4885
|
+
|
|
4886
|
+
`;
|
|
4887
|
+
blocks.push({
|
|
4888
|
+
type: "text",
|
|
4889
|
+
text: `${label}[Voice transcript]: ${transcript}`
|
|
4890
|
+
});
|
|
4891
|
+
this.logger.info({ transcriptLength: transcript.length }, "Voice message transcribed");
|
|
4892
|
+
return blocks.length === 1 ? blocks[0].type === "text" ? blocks[0].text : blocks : blocks;
|
|
4893
|
+
} catch (err) {
|
|
4894
|
+
this.logger.error({ err }, "Voice transcription failed");
|
|
4895
|
+
blocks.push({
|
|
4896
|
+
type: "text",
|
|
4897
|
+
text: "[Voice message could not be transcribed]"
|
|
4898
|
+
});
|
|
4899
|
+
}
|
|
4900
|
+
} else {
|
|
4901
|
+
blocks.push({
|
|
4902
|
+
type: "text",
|
|
4903
|
+
text: "[Voice message received but speech-to-text is not configured. Add speech config to enable transcription.]"
|
|
4904
|
+
});
|
|
4905
|
+
}
|
|
4906
|
+
} else if ((attachment.type === "document" || attachment.type === "video" || attachment.type === "other") && attachment.data) {
|
|
4907
|
+
const savedPath = this.saveToInbox(attachment);
|
|
4908
|
+
if (savedPath) {
|
|
4909
|
+
const isTextFile = this.isTextMimeType(attachment.mimeType);
|
|
4910
|
+
let fileNote = `[File received: "${attachment.fileName ?? "unknown"}" (${this.formatBytes(attachment.data.length)}, ${attachment.mimeType ?? "unknown type"})]
|
|
4911
|
+
[Saved to: ${savedPath}]`;
|
|
4912
|
+
if (isTextFile && attachment.data.length <= MAX_INLINE_FILE_SIZE) {
|
|
4913
|
+
const textContent = attachment.data.toString("utf-8");
|
|
4914
|
+
fileNote += `
|
|
4915
|
+
[File content]:
|
|
4916
|
+
${textContent}`;
|
|
4917
|
+
}
|
|
4918
|
+
blocks.push({ type: "text", text: fileNote });
|
|
4919
|
+
this.logger.info({ fileName: attachment.fileName, savedPath, size: attachment.data.length }, "File saved to inbox");
|
|
4920
|
+
}
|
|
4921
|
+
}
|
|
4922
|
+
}
|
|
4923
|
+
const skipTexts = ["[Photo]", "[Voice message]", "[Video]", "[Video note]", "[Document]", "[File]"];
|
|
4924
|
+
if (message.text && !skipTexts.includes(message.text)) {
|
|
4925
|
+
blocks.push({ type: "text", text: message.text });
|
|
4926
|
+
} else if (blocks.some((b) => b.type === "image") && !blocks.some((b) => b.type === "text")) {
|
|
4927
|
+
blocks.push({ type: "text", text: "What do you see in this image?" });
|
|
4928
|
+
} else if (blocks.length === 0) {
|
|
4929
|
+
blocks.push({ type: "text", text: message.text || "(empty message)" });
|
|
4930
|
+
}
|
|
4931
|
+
return blocks;
|
|
4932
|
+
}
|
|
4933
|
+
/**
|
|
4934
|
+
* Save an attachment to the inbox directory.
|
|
4935
|
+
* Returns the saved file path, or undefined on failure.
|
|
4936
|
+
*/
|
|
4937
|
+
saveToInbox(attachment) {
|
|
4938
|
+
if (!attachment.data)
|
|
4939
|
+
return void 0;
|
|
4940
|
+
const inboxDir = this.inboxPath ?? path7.resolve("./data/inbox");
|
|
4941
|
+
try {
|
|
4942
|
+
fs5.mkdirSync(inboxDir, { recursive: true });
|
|
4943
|
+
} catch {
|
|
4944
|
+
this.logger.error({ inboxDir }, "Cannot create inbox directory");
|
|
4945
|
+
return void 0;
|
|
4946
|
+
}
|
|
4947
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4948
|
+
const originalName = attachment.fileName ?? `file_${timestamp}`;
|
|
4949
|
+
const safeName = originalName.replace(/[<>:"/\\|?*]/g, "_");
|
|
4950
|
+
const fileName = `${timestamp}_${safeName}`;
|
|
4951
|
+
const filePath = path7.join(inboxDir, fileName);
|
|
4952
|
+
try {
|
|
4953
|
+
fs5.writeFileSync(filePath, attachment.data);
|
|
4954
|
+
return filePath;
|
|
4955
|
+
} catch (err) {
|
|
4956
|
+
this.logger.error({ err, filePath }, "Failed to save file to inbox");
|
|
4957
|
+
return void 0;
|
|
4958
|
+
}
|
|
4959
|
+
}
|
|
4960
|
+
isTextMimeType(mimeType) {
|
|
4961
|
+
if (!mimeType)
|
|
4962
|
+
return false;
|
|
4963
|
+
const textTypes = [
|
|
4964
|
+
"text/",
|
|
4965
|
+
"application/json",
|
|
4966
|
+
"application/xml",
|
|
4967
|
+
"application/javascript",
|
|
4968
|
+
"application/typescript",
|
|
4969
|
+
"application/x-yaml",
|
|
4970
|
+
"application/yaml",
|
|
4971
|
+
"application/toml",
|
|
4972
|
+
"application/x-sh",
|
|
4973
|
+
"application/sql",
|
|
4974
|
+
"application/csv",
|
|
4975
|
+
"application/x-csv"
|
|
4976
|
+
];
|
|
4977
|
+
return textTypes.some((t) => mimeType.startsWith(t));
|
|
4978
|
+
}
|
|
4979
|
+
formatBytes(bytes) {
|
|
4980
|
+
if (bytes < 1024)
|
|
4981
|
+
return `${bytes} B`;
|
|
4982
|
+
if (bytes < 1024 * 1024)
|
|
4983
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
4984
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
4985
|
+
}
|
|
4092
4986
|
};
|
|
4093
4987
|
}
|
|
4094
4988
|
});
|
|
@@ -4142,6 +5036,64 @@ var init_reminder_scheduler = __esm({
|
|
|
4142
5036
|
}
|
|
4143
5037
|
});
|
|
4144
5038
|
|
|
5039
|
+
// ../core/dist/speech-transcriber.js
|
|
5040
|
+
var SpeechTranscriber;
|
|
5041
|
+
var init_speech_transcriber = __esm({
|
|
5042
|
+
"../core/dist/speech-transcriber.js"() {
|
|
5043
|
+
"use strict";
|
|
5044
|
+
SpeechTranscriber = class {
|
|
5045
|
+
logger;
|
|
5046
|
+
apiKey;
|
|
5047
|
+
baseUrl;
|
|
5048
|
+
constructor(config, logger) {
|
|
5049
|
+
this.logger = logger;
|
|
5050
|
+
this.apiKey = config.apiKey;
|
|
5051
|
+
if (config.provider === "groq") {
|
|
5052
|
+
this.baseUrl = config.baseUrl ?? "https://api.groq.com/openai/v1";
|
|
5053
|
+
} else {
|
|
5054
|
+
this.baseUrl = config.baseUrl ?? "https://api.openai.com/v1";
|
|
5055
|
+
}
|
|
5056
|
+
}
|
|
5057
|
+
async transcribe(audioBuffer, mimeType) {
|
|
5058
|
+
const ext = this.mimeToExtension(mimeType);
|
|
5059
|
+
const formData = new FormData();
|
|
5060
|
+
formData.append("file", new Blob([audioBuffer], { type: mimeType }), `audio.${ext}`);
|
|
5061
|
+
formData.append("model", "whisper-1");
|
|
5062
|
+
try {
|
|
5063
|
+
const response = await fetch(`${this.baseUrl}/audio/transcriptions`, {
|
|
5064
|
+
method: "POST",
|
|
5065
|
+
headers: {
|
|
5066
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
5067
|
+
},
|
|
5068
|
+
body: formData
|
|
5069
|
+
});
|
|
5070
|
+
if (!response.ok) {
|
|
5071
|
+
const errorText = await response.text();
|
|
5072
|
+
throw new Error(`Whisper API ${response.status}: ${errorText}`);
|
|
5073
|
+
}
|
|
5074
|
+
const data = await response.json();
|
|
5075
|
+
this.logger.info({ textLength: data.text.length }, "Voice transcribed");
|
|
5076
|
+
return data.text;
|
|
5077
|
+
} catch (err) {
|
|
5078
|
+
this.logger.error({ err }, "Voice transcription failed");
|
|
5079
|
+
throw err;
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5082
|
+
mimeToExtension(mimeType) {
|
|
5083
|
+
const map = {
|
|
5084
|
+
"audio/ogg": "ogg",
|
|
5085
|
+
"audio/mpeg": "mp3",
|
|
5086
|
+
"audio/mp4": "m4a",
|
|
5087
|
+
"audio/wav": "wav",
|
|
5088
|
+
"audio/webm": "webm",
|
|
5089
|
+
"audio/x-m4a": "m4a"
|
|
5090
|
+
};
|
|
5091
|
+
return map[mimeType] ?? "ogg";
|
|
5092
|
+
}
|
|
5093
|
+
};
|
|
5094
|
+
}
|
|
5095
|
+
});
|
|
5096
|
+
|
|
4145
5097
|
// ../messaging/dist/adapter.js
|
|
4146
5098
|
import { EventEmitter } from "node:events";
|
|
4147
5099
|
var MessagingAdapter;
|
|
@@ -4150,6 +5102,12 @@ var init_adapter = __esm({
|
|
|
4150
5102
|
"use strict";
|
|
4151
5103
|
MessagingAdapter = class extends EventEmitter {
|
|
4152
5104
|
status = "disconnected";
|
|
5105
|
+
async sendPhoto(_chatId, _photo, _caption) {
|
|
5106
|
+
return void 0;
|
|
5107
|
+
}
|
|
5108
|
+
async sendFile(_chatId, _file, _fileName, _caption) {
|
|
5109
|
+
return void 0;
|
|
5110
|
+
}
|
|
4153
5111
|
getStatus() {
|
|
4154
5112
|
return this.status;
|
|
4155
5113
|
}
|
|
@@ -4158,7 +5116,7 @@ var init_adapter = __esm({
|
|
|
4158
5116
|
});
|
|
4159
5117
|
|
|
4160
5118
|
// ../messaging/dist/adapters/telegram.js
|
|
4161
|
-
import { Bot } from "grammy";
|
|
5119
|
+
import { Bot, InputFile } from "grammy";
|
|
4162
5120
|
function mapParseMode(mode) {
|
|
4163
5121
|
if (mode === "markdown")
|
|
4164
5122
|
return "MarkdownV2";
|
|
@@ -4181,21 +5139,65 @@ var init_telegram = __esm({
|
|
|
4181
5139
|
async connect() {
|
|
4182
5140
|
this.status = "connecting";
|
|
4183
5141
|
this.bot.on("message:text", (ctx) => {
|
|
5142
|
+
this.emit("message", this.normalizeMessage(ctx.message, ctx.message.text));
|
|
5143
|
+
});
|
|
5144
|
+
this.bot.on("message:photo", async (ctx) => {
|
|
4184
5145
|
const msg = ctx.message;
|
|
4185
|
-
const
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
5146
|
+
const caption = msg.caption ?? "";
|
|
5147
|
+
const text = caption || "[Photo]";
|
|
5148
|
+
const photo = msg.photo[msg.photo.length - 1];
|
|
5149
|
+
const attachment = await this.downloadAttachment(photo.file_id, "image", "image/jpeg");
|
|
5150
|
+
const normalized = this.normalizeMessage(msg, text);
|
|
5151
|
+
normalized.attachments = attachment ? [attachment] : void 0;
|
|
5152
|
+
this.emit("message", normalized);
|
|
5153
|
+
});
|
|
5154
|
+
this.bot.on("message:voice", async (ctx) => {
|
|
5155
|
+
const msg = ctx.message;
|
|
5156
|
+
const attachment = await this.downloadAttachment(msg.voice.file_id, "audio", msg.voice.mime_type ?? "audio/ogg");
|
|
5157
|
+
const normalized = this.normalizeMessage(msg, "[Voice message]");
|
|
5158
|
+
normalized.attachments = attachment ? [attachment] : void 0;
|
|
5159
|
+
this.emit("message", normalized);
|
|
5160
|
+
});
|
|
5161
|
+
this.bot.on("message:audio", async (ctx) => {
|
|
5162
|
+
const msg = ctx.message;
|
|
5163
|
+
const caption = msg.caption ?? "";
|
|
5164
|
+
const text = caption || `[Audio: ${msg.audio.file_name ?? "audio"}]`;
|
|
5165
|
+
const attachment = await this.downloadAttachment(msg.audio.file_id, "audio", msg.audio.mime_type ?? "audio/mpeg");
|
|
5166
|
+
const normalized = this.normalizeMessage(msg, text);
|
|
5167
|
+
normalized.attachments = attachment ? [attachment] : void 0;
|
|
5168
|
+
this.emit("message", normalized);
|
|
5169
|
+
});
|
|
5170
|
+
this.bot.on("message:video", async (ctx) => {
|
|
5171
|
+
const msg = ctx.message;
|
|
5172
|
+
const caption = msg.caption ?? "";
|
|
5173
|
+
const text = caption || "[Video]";
|
|
5174
|
+
const attachment = await this.downloadAttachment(msg.video.file_id, "video", msg.video.mime_type ?? "video/mp4");
|
|
5175
|
+
const normalized = this.normalizeMessage(msg, text);
|
|
5176
|
+
normalized.attachments = attachment ? [attachment] : void 0;
|
|
5177
|
+
this.emit("message", normalized);
|
|
5178
|
+
});
|
|
5179
|
+
this.bot.on("message:document", async (ctx) => {
|
|
5180
|
+
const msg = ctx.message;
|
|
5181
|
+
const doc = msg.document;
|
|
5182
|
+
const caption = msg.caption ?? "";
|
|
5183
|
+
const text = caption || `[Document: ${doc.file_name ?? "file"}]`;
|
|
5184
|
+
const attachment = await this.downloadAttachment(doc.file_id, "document", doc.mime_type ?? "application/octet-stream", doc.file_name);
|
|
5185
|
+
const normalized = this.normalizeMessage(msg, text);
|
|
5186
|
+
normalized.attachments = attachment ? [attachment] : void 0;
|
|
5187
|
+
this.emit("message", normalized);
|
|
5188
|
+
});
|
|
5189
|
+
this.bot.on("message:video_note", async (ctx) => {
|
|
5190
|
+
const msg = ctx.message;
|
|
5191
|
+
const attachment = await this.downloadAttachment(msg.video_note.file_id, "video", "video/mp4");
|
|
5192
|
+
const normalized = this.normalizeMessage(msg, "[Video note]");
|
|
5193
|
+
normalized.attachments = attachment ? [attachment] : void 0;
|
|
4197
5194
|
this.emit("message", normalized);
|
|
4198
5195
|
});
|
|
5196
|
+
this.bot.on("message:sticker", (ctx) => {
|
|
5197
|
+
const msg = ctx.message;
|
|
5198
|
+
const emoji = msg.sticker.emoji ?? "\u{1F3F7}\uFE0F";
|
|
5199
|
+
this.emit("message", this.normalizeMessage(msg, `[Sticker: ${emoji}]`));
|
|
5200
|
+
});
|
|
4199
5201
|
this.bot.catch((err) => {
|
|
4200
5202
|
this.emit("error", err.error);
|
|
4201
5203
|
});
|
|
@@ -4224,6 +5226,50 @@ var init_telegram = __esm({
|
|
|
4224
5226
|
async deleteMessage(chatId, messageId) {
|
|
4225
5227
|
await this.bot.api.deleteMessage(Number(chatId), Number(messageId));
|
|
4226
5228
|
}
|
|
5229
|
+
async sendPhoto(chatId, photo, caption) {
|
|
5230
|
+
const result = await this.bot.api.sendPhoto(Number(chatId), new InputFile(photo, "image.png"), { caption });
|
|
5231
|
+
return String(result.message_id);
|
|
5232
|
+
}
|
|
5233
|
+
async sendFile(chatId, file, fileName, caption) {
|
|
5234
|
+
const result = await this.bot.api.sendDocument(Number(chatId), new InputFile(file, fileName), { caption });
|
|
5235
|
+
return String(result.message_id);
|
|
5236
|
+
}
|
|
5237
|
+
normalizeMessage(msg, text) {
|
|
5238
|
+
return {
|
|
5239
|
+
id: String(msg.message_id),
|
|
5240
|
+
platform: "telegram",
|
|
5241
|
+
chatId: String(msg.chat.id),
|
|
5242
|
+
chatType: msg.chat.type === "private" ? "dm" : "group",
|
|
5243
|
+
userId: String(msg.from.id),
|
|
5244
|
+
userName: msg.from.username ?? String(msg.from.id),
|
|
5245
|
+
displayName: [msg.from.first_name, msg.from.last_name].filter(Boolean).join(" "),
|
|
5246
|
+
text,
|
|
5247
|
+
timestamp: new Date(msg.date * 1e3),
|
|
5248
|
+
replyToMessageId: msg.reply_to_message ? String(msg.reply_to_message.message_id) : void 0
|
|
5249
|
+
};
|
|
5250
|
+
}
|
|
5251
|
+
async downloadAttachment(fileId, type, mimeType, fileName) {
|
|
5252
|
+
try {
|
|
5253
|
+
const file = await this.bot.api.getFile(fileId);
|
|
5254
|
+
const filePath = file.file_path;
|
|
5255
|
+
if (!filePath)
|
|
5256
|
+
return void 0;
|
|
5257
|
+
const url = `https://api.telegram.org/file/bot${this.bot.token}/${filePath}`;
|
|
5258
|
+
const response = await fetch(url);
|
|
5259
|
+
if (!response.ok)
|
|
5260
|
+
return void 0;
|
|
5261
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
5262
|
+
return {
|
|
5263
|
+
type,
|
|
5264
|
+
mimeType,
|
|
5265
|
+
fileName: fileName ?? filePath.split("/").pop(),
|
|
5266
|
+
size: buffer.length,
|
|
5267
|
+
data: buffer
|
|
5268
|
+
};
|
|
5269
|
+
} catch {
|
|
5270
|
+
return void 0;
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
4227
5273
|
};
|
|
4228
5274
|
}
|
|
4229
5275
|
});
|
|
@@ -4253,22 +5299,29 @@ var init_discord = __esm({
|
|
|
4253
5299
|
GatewayIntentBits.DirectMessages
|
|
4254
5300
|
]
|
|
4255
5301
|
});
|
|
4256
|
-
this.client.on(Events.MessageCreate, (message) => {
|
|
5302
|
+
this.client.on(Events.MessageCreate, async (message) => {
|
|
4257
5303
|
if (message.author.bot)
|
|
4258
5304
|
return;
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
5305
|
+
try {
|
|
5306
|
+
const attachments = await this.downloadAttachments(message);
|
|
5307
|
+
const text = message.content || this.inferTextFromAttachments(attachments);
|
|
5308
|
+
const normalized = {
|
|
5309
|
+
id: message.id,
|
|
5310
|
+
platform: "discord",
|
|
5311
|
+
chatId: message.channelId,
|
|
5312
|
+
chatType: message.channel.isDMBased() ? "dm" : "group",
|
|
5313
|
+
userId: message.author.id,
|
|
5314
|
+
userName: message.author.username,
|
|
5315
|
+
displayName: message.author.displayName,
|
|
5316
|
+
text,
|
|
5317
|
+
timestamp: message.createdAt,
|
|
5318
|
+
replyToMessageId: message.reference?.messageId ?? void 0,
|
|
5319
|
+
attachments: attachments.length > 0 ? attachments : void 0
|
|
5320
|
+
};
|
|
5321
|
+
this.emit("message", normalized);
|
|
5322
|
+
} catch (err) {
|
|
5323
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
5324
|
+
}
|
|
4272
5325
|
});
|
|
4273
5326
|
this.client.on(Events.ClientReady, () => {
|
|
4274
5327
|
this.status = "connected";
|
|
@@ -4320,6 +5373,82 @@ var init_discord = __esm({
|
|
|
4320
5373
|
const message = await channel.messages.fetch(messageId);
|
|
4321
5374
|
await message.delete();
|
|
4322
5375
|
}
|
|
5376
|
+
async sendPhoto(chatId, photo, caption) {
|
|
5377
|
+
if (!this.client)
|
|
5378
|
+
return void 0;
|
|
5379
|
+
const channel = await this.client.channels.fetch(chatId);
|
|
5380
|
+
if (!channel?.isTextBased() || !("send" in channel))
|
|
5381
|
+
return void 0;
|
|
5382
|
+
const msg = await channel.send({
|
|
5383
|
+
content: caption,
|
|
5384
|
+
files: [{ attachment: photo, name: "image.png" }]
|
|
5385
|
+
});
|
|
5386
|
+
return msg.id;
|
|
5387
|
+
}
|
|
5388
|
+
async sendFile(chatId, file, fileName, caption) {
|
|
5389
|
+
if (!this.client)
|
|
5390
|
+
return void 0;
|
|
5391
|
+
const channel = await this.client.channels.fetch(chatId);
|
|
5392
|
+
if (!channel?.isTextBased() || !("send" in channel))
|
|
5393
|
+
return void 0;
|
|
5394
|
+
const msg = await channel.send({
|
|
5395
|
+
content: caption,
|
|
5396
|
+
files: [{ attachment: file, name: fileName }]
|
|
5397
|
+
});
|
|
5398
|
+
return msg.id;
|
|
5399
|
+
}
|
|
5400
|
+
// ── Private helpers ──────────────────────────────────────────────
|
|
5401
|
+
async downloadAttachments(message) {
|
|
5402
|
+
const result = [];
|
|
5403
|
+
const discordAttachments = message.attachments;
|
|
5404
|
+
if (!discordAttachments || discordAttachments.size === 0)
|
|
5405
|
+
return result;
|
|
5406
|
+
for (const [, att] of discordAttachments) {
|
|
5407
|
+
try {
|
|
5408
|
+
const res = await fetch(att.url);
|
|
5409
|
+
if (!res.ok)
|
|
5410
|
+
continue;
|
|
5411
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
5412
|
+
const data = Buffer.from(arrayBuffer);
|
|
5413
|
+
const type = this.classifyContentType(att.contentType);
|
|
5414
|
+
result.push({
|
|
5415
|
+
type,
|
|
5416
|
+
url: att.url,
|
|
5417
|
+
mimeType: att.contentType ?? void 0,
|
|
5418
|
+
fileName: att.name ?? void 0,
|
|
5419
|
+
size: att.size ?? data.length,
|
|
5420
|
+
data
|
|
5421
|
+
});
|
|
5422
|
+
} catch {
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5425
|
+
return result;
|
|
5426
|
+
}
|
|
5427
|
+
classifyContentType(contentType) {
|
|
5428
|
+
if (!contentType)
|
|
5429
|
+
return "other";
|
|
5430
|
+
if (contentType.startsWith("image/"))
|
|
5431
|
+
return "image";
|
|
5432
|
+
if (contentType.startsWith("audio/"))
|
|
5433
|
+
return "audio";
|
|
5434
|
+
if (contentType.startsWith("video/"))
|
|
5435
|
+
return "video";
|
|
5436
|
+
return "document";
|
|
5437
|
+
}
|
|
5438
|
+
inferTextFromAttachments(attachments) {
|
|
5439
|
+
if (attachments.length === 0)
|
|
5440
|
+
return "";
|
|
5441
|
+
const types = attachments.map((a) => a.type);
|
|
5442
|
+
if (types.includes("image"))
|
|
5443
|
+
return "[Photo]";
|
|
5444
|
+
if (types.includes("audio"))
|
|
5445
|
+
return "[Voice message]";
|
|
5446
|
+
if (types.includes("video"))
|
|
5447
|
+
return "[Video]";
|
|
5448
|
+
if (types.includes("document"))
|
|
5449
|
+
return "[Document]";
|
|
5450
|
+
return "[File]";
|
|
5451
|
+
}
|
|
4323
5452
|
};
|
|
4324
5453
|
}
|
|
4325
5454
|
});
|
|
@@ -4338,7 +5467,7 @@ var init_matrix = __esm({
|
|
|
4338
5467
|
botUserId;
|
|
4339
5468
|
constructor(homeserverUrl, accessToken, botUserId) {
|
|
4340
5469
|
super();
|
|
4341
|
-
this.homeserverUrl = homeserverUrl;
|
|
5470
|
+
this.homeserverUrl = homeserverUrl.replace(/\/+$/, "");
|
|
4342
5471
|
this.accessToken = accessToken;
|
|
4343
5472
|
this.botUserId = botUserId;
|
|
4344
5473
|
}
|
|
@@ -4348,23 +5477,20 @@ var init_matrix = __esm({
|
|
|
4348
5477
|
const storageProvider = new SimpleFsStorageProvider("./data/matrix-storage");
|
|
4349
5478
|
this.client = new MatrixClient(this.homeserverUrl, this.accessToken, storageProvider);
|
|
4350
5479
|
AutojoinRoomsMixin.setupOnClient(this.client);
|
|
4351
|
-
this.client.on("room.message", (roomId, event) => {
|
|
5480
|
+
this.client.on("room.message", async (roomId, event) => {
|
|
4352
5481
|
if (event.sender === this.botUserId)
|
|
4353
5482
|
return;
|
|
4354
|
-
|
|
5483
|
+
const msgtype = event.content?.msgtype;
|
|
5484
|
+
if (!msgtype)
|
|
4355
5485
|
return;
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
timestamp: new Date(event.origin_server_ts),
|
|
4365
|
-
replyToMessageId: event.content["m.relates_to"]?.["m.in_reply_to"]?.event_id
|
|
4366
|
-
};
|
|
4367
|
-
this.emit("message", normalized);
|
|
5486
|
+
try {
|
|
5487
|
+
const message = await this.normalizeEvent(roomId, event, msgtype);
|
|
5488
|
+
if (message) {
|
|
5489
|
+
this.emit("message", message);
|
|
5490
|
+
}
|
|
5491
|
+
} catch (err) {
|
|
5492
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
5493
|
+
}
|
|
4368
5494
|
});
|
|
4369
5495
|
await this.client.start();
|
|
4370
5496
|
this.status = "connected";
|
|
@@ -4396,6 +5522,144 @@ var init_matrix = __esm({
|
|
|
4396
5522
|
async deleteMessage(chatId, messageId) {
|
|
4397
5523
|
await this.client.redactEvent(chatId, messageId);
|
|
4398
5524
|
}
|
|
5525
|
+
async sendPhoto(chatId, photo, caption) {
|
|
5526
|
+
const mxcUrl = await this.client.uploadContent(photo, "image/png", "image.png");
|
|
5527
|
+
const content = {
|
|
5528
|
+
msgtype: "m.image",
|
|
5529
|
+
body: caption ?? "image.png",
|
|
5530
|
+
url: mxcUrl,
|
|
5531
|
+
info: {
|
|
5532
|
+
mimetype: "image/png",
|
|
5533
|
+
size: photo.length
|
|
5534
|
+
}
|
|
5535
|
+
};
|
|
5536
|
+
const eventId = await this.client.sendEvent(chatId, "m.room.message", content);
|
|
5537
|
+
return eventId;
|
|
5538
|
+
}
|
|
5539
|
+
async sendFile(chatId, file, fileName, caption) {
|
|
5540
|
+
const mimeType = this.guessMimeType(fileName);
|
|
5541
|
+
const mxcUrl = await this.client.uploadContent(file, mimeType, fileName);
|
|
5542
|
+
const content = {
|
|
5543
|
+
msgtype: "m.file",
|
|
5544
|
+
body: caption ?? fileName,
|
|
5545
|
+
filename: fileName,
|
|
5546
|
+
url: mxcUrl,
|
|
5547
|
+
info: {
|
|
5548
|
+
mimetype: mimeType,
|
|
5549
|
+
size: file.length
|
|
5550
|
+
}
|
|
5551
|
+
};
|
|
5552
|
+
const eventId = await this.client.sendEvent(chatId, "m.room.message", content);
|
|
5553
|
+
return eventId;
|
|
5554
|
+
}
|
|
5555
|
+
// ── Private helpers ──────────────────────────────────────────────
|
|
5556
|
+
async normalizeEvent(roomId, event, msgtype) {
|
|
5557
|
+
const base = {
|
|
5558
|
+
id: event.event_id,
|
|
5559
|
+
platform: "matrix",
|
|
5560
|
+
chatId: roomId,
|
|
5561
|
+
chatType: "group",
|
|
5562
|
+
userId: event.sender,
|
|
5563
|
+
userName: event.sender.split(":")[0].slice(1),
|
|
5564
|
+
timestamp: new Date(event.origin_server_ts),
|
|
5565
|
+
replyToMessageId: event.content["m.relates_to"]?.["m.in_reply_to"]?.event_id
|
|
5566
|
+
};
|
|
5567
|
+
switch (msgtype) {
|
|
5568
|
+
case "m.text":
|
|
5569
|
+
return { ...base, text: event.content.body };
|
|
5570
|
+
case "m.image": {
|
|
5571
|
+
const attachment = await this.downloadAttachment(event.content, "image");
|
|
5572
|
+
return {
|
|
5573
|
+
...base,
|
|
5574
|
+
text: event.content.body ?? "[Photo]",
|
|
5575
|
+
attachments: attachment ? [attachment] : void 0
|
|
5576
|
+
};
|
|
5577
|
+
}
|
|
5578
|
+
case "m.audio": {
|
|
5579
|
+
const attachment = await this.downloadAttachment(event.content, "audio");
|
|
5580
|
+
return {
|
|
5581
|
+
...base,
|
|
5582
|
+
text: event.content.body ?? "[Voice message]",
|
|
5583
|
+
attachments: attachment ? [attachment] : void 0
|
|
5584
|
+
};
|
|
5585
|
+
}
|
|
5586
|
+
case "m.video": {
|
|
5587
|
+
const attachment = await this.downloadAttachment(event.content, "video");
|
|
5588
|
+
return {
|
|
5589
|
+
...base,
|
|
5590
|
+
text: event.content.body ?? "[Video]",
|
|
5591
|
+
attachments: attachment ? [attachment] : void 0
|
|
5592
|
+
};
|
|
5593
|
+
}
|
|
5594
|
+
case "m.file": {
|
|
5595
|
+
const attachment = await this.downloadAttachment(event.content, "document");
|
|
5596
|
+
return {
|
|
5597
|
+
...base,
|
|
5598
|
+
text: event.content.body ?? "[Document]",
|
|
5599
|
+
attachments: attachment ? [attachment] : void 0
|
|
5600
|
+
};
|
|
5601
|
+
}
|
|
5602
|
+
default:
|
|
5603
|
+
if (event.content.body) {
|
|
5604
|
+
return { ...base, text: event.content.body };
|
|
5605
|
+
}
|
|
5606
|
+
return void 0;
|
|
5607
|
+
}
|
|
5608
|
+
}
|
|
5609
|
+
/**
|
|
5610
|
+
* Download a Matrix media file from an mxc:// URL.
|
|
5611
|
+
* Uses the /_matrix/media/v3/download endpoint.
|
|
5612
|
+
*/
|
|
5613
|
+
async downloadAttachment(content, type) {
|
|
5614
|
+
const mxcUrl = content.url;
|
|
5615
|
+
if (!mxcUrl || !mxcUrl.startsWith("mxc://"))
|
|
5616
|
+
return void 0;
|
|
5617
|
+
const info = content.info ?? {};
|
|
5618
|
+
const mimeType = info.mimetype;
|
|
5619
|
+
const size = info.size;
|
|
5620
|
+
const fileName = content.filename ?? content.body ?? "file";
|
|
5621
|
+
try {
|
|
5622
|
+
const mxcParts = mxcUrl.slice(6);
|
|
5623
|
+
const downloadUrl = `${this.homeserverUrl}/_matrix/media/v3/download/${mxcParts}`;
|
|
5624
|
+
const res = await fetch(downloadUrl, {
|
|
5625
|
+
headers: { Authorization: `Bearer ${this.accessToken}` }
|
|
5626
|
+
});
|
|
5627
|
+
if (!res.ok)
|
|
5628
|
+
return void 0;
|
|
5629
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
5630
|
+
const data = Buffer.from(arrayBuffer);
|
|
5631
|
+
return {
|
|
5632
|
+
type,
|
|
5633
|
+
mimeType,
|
|
5634
|
+
fileName,
|
|
5635
|
+
size: size ?? data.length,
|
|
5636
|
+
data
|
|
5637
|
+
};
|
|
5638
|
+
} catch {
|
|
5639
|
+
return void 0;
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5642
|
+
guessMimeType(fileName) {
|
|
5643
|
+
const ext = fileName.split(".").pop()?.toLowerCase();
|
|
5644
|
+
const mimeMap = {
|
|
5645
|
+
pdf: "application/pdf",
|
|
5646
|
+
txt: "text/plain",
|
|
5647
|
+
json: "application/json",
|
|
5648
|
+
csv: "text/csv",
|
|
5649
|
+
png: "image/png",
|
|
5650
|
+
jpg: "image/jpeg",
|
|
5651
|
+
jpeg: "image/jpeg",
|
|
5652
|
+
gif: "image/gif",
|
|
5653
|
+
mp3: "audio/mpeg",
|
|
5654
|
+
ogg: "audio/ogg",
|
|
5655
|
+
mp4: "video/mp4",
|
|
5656
|
+
zip: "application/zip",
|
|
5657
|
+
doc: "application/msword",
|
|
5658
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
5659
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
5660
|
+
};
|
|
5661
|
+
return mimeMap[ext ?? ""] ?? "application/octet-stream";
|
|
5662
|
+
}
|
|
4399
5663
|
};
|
|
4400
5664
|
}
|
|
4401
5665
|
});
|
|
@@ -4409,6 +5673,7 @@ var init_whatsapp = __esm({
|
|
|
4409
5673
|
WhatsAppAdapter = class extends MessagingAdapter {
|
|
4410
5674
|
platform = "whatsapp";
|
|
4411
5675
|
socket;
|
|
5676
|
+
downloadMedia;
|
|
4412
5677
|
dataPath;
|
|
4413
5678
|
constructor(dataPath) {
|
|
4414
5679
|
super();
|
|
@@ -4417,7 +5682,9 @@ var init_whatsapp = __esm({
|
|
|
4417
5682
|
async connect() {
|
|
4418
5683
|
this.status = "connecting";
|
|
4419
5684
|
const baileys = await import("@whiskeysockets/baileys");
|
|
4420
|
-
const
|
|
5685
|
+
const mod = baileys.default ?? baileys;
|
|
5686
|
+
const { makeWASocket, useMultiFileAuthState, DisconnectReason, downloadMediaMessage } = mod;
|
|
5687
|
+
this.downloadMedia = downloadMediaMessage;
|
|
4421
5688
|
const { state, saveCreds } = await useMultiFileAuthState(this.dataPath);
|
|
4422
5689
|
this.socket = makeWASocket({
|
|
4423
5690
|
auth: state,
|
|
@@ -4447,21 +5714,9 @@ var init_whatsapp = __esm({
|
|
|
4447
5714
|
continue;
|
|
4448
5715
|
if (message.key.fromMe)
|
|
4449
5716
|
continue;
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
const normalized = {
|
|
4454
|
-
id: message.key.id ?? "",
|
|
4455
|
-
platform: "whatsapp",
|
|
4456
|
-
chatId: message.key.remoteJid ?? "",
|
|
4457
|
-
chatType: message.key.remoteJid?.endsWith("@g.us") ? "group" : "dm",
|
|
4458
|
-
userId: message.key.participant ?? message.key.remoteJid ?? "",
|
|
4459
|
-
userName: message.pushName ?? message.key.participant ?? message.key.remoteJid ?? "",
|
|
4460
|
-
text,
|
|
4461
|
-
timestamp: new Date(message.messageTimestamp * 1e3),
|
|
4462
|
-
replyToMessageId: message.message.extendedTextMessage?.contextInfo?.stanzaId ?? void 0
|
|
4463
|
-
};
|
|
4464
|
-
this.emit("message", normalized);
|
|
5717
|
+
this.processMessage(message).catch((err) => {
|
|
5718
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
5719
|
+
});
|
|
4465
5720
|
}
|
|
4466
5721
|
});
|
|
4467
5722
|
}
|
|
@@ -4499,6 +5754,128 @@ var init_whatsapp = __esm({
|
|
|
4499
5754
|
}
|
|
4500
5755
|
});
|
|
4501
5756
|
}
|
|
5757
|
+
async sendPhoto(chatId, photo, caption) {
|
|
5758
|
+
const msg = await this.socket.sendMessage(chatId, {
|
|
5759
|
+
image: photo,
|
|
5760
|
+
caption
|
|
5761
|
+
});
|
|
5762
|
+
return msg?.key?.id;
|
|
5763
|
+
}
|
|
5764
|
+
async sendFile(chatId, file, fileName, caption) {
|
|
5765
|
+
const msg = await this.socket.sendMessage(chatId, {
|
|
5766
|
+
document: file,
|
|
5767
|
+
fileName,
|
|
5768
|
+
caption,
|
|
5769
|
+
mimetype: this.guessMimeType(fileName)
|
|
5770
|
+
});
|
|
5771
|
+
return msg?.key?.id;
|
|
5772
|
+
}
|
|
5773
|
+
// ── Private helpers ──────────────────────────────────────────────
|
|
5774
|
+
async processMessage(message) {
|
|
5775
|
+
const msg = message.message;
|
|
5776
|
+
const text = msg.conversation ?? msg.extendedTextMessage?.text ?? msg.imageMessage?.caption ?? msg.videoMessage?.caption ?? msg.documentMessage?.caption ?? "";
|
|
5777
|
+
const attachments = [];
|
|
5778
|
+
let fallbackText = text;
|
|
5779
|
+
if (msg.imageMessage) {
|
|
5780
|
+
const data = await this.downloadMediaSafe(message);
|
|
5781
|
+
if (data) {
|
|
5782
|
+
attachments.push({
|
|
5783
|
+
type: "image",
|
|
5784
|
+
mimeType: msg.imageMessage.mimetype ?? "image/jpeg",
|
|
5785
|
+
size: msg.imageMessage.fileLength ?? data.length,
|
|
5786
|
+
data
|
|
5787
|
+
});
|
|
5788
|
+
}
|
|
5789
|
+
if (!fallbackText)
|
|
5790
|
+
fallbackText = "[Photo]";
|
|
5791
|
+
} else if (msg.audioMessage) {
|
|
5792
|
+
const data = await this.downloadMediaSafe(message);
|
|
5793
|
+
if (data) {
|
|
5794
|
+
attachments.push({
|
|
5795
|
+
type: "audio",
|
|
5796
|
+
mimeType: msg.audioMessage.mimetype ?? "audio/ogg",
|
|
5797
|
+
size: msg.audioMessage.fileLength ?? data.length,
|
|
5798
|
+
data
|
|
5799
|
+
});
|
|
5800
|
+
}
|
|
5801
|
+
if (!fallbackText)
|
|
5802
|
+
fallbackText = "[Voice message]";
|
|
5803
|
+
} else if (msg.videoMessage) {
|
|
5804
|
+
const data = await this.downloadMediaSafe(message);
|
|
5805
|
+
if (data) {
|
|
5806
|
+
attachments.push({
|
|
5807
|
+
type: "video",
|
|
5808
|
+
mimeType: msg.videoMessage.mimetype ?? "video/mp4",
|
|
5809
|
+
size: msg.videoMessage.fileLength ?? data.length,
|
|
5810
|
+
data
|
|
5811
|
+
});
|
|
5812
|
+
}
|
|
5813
|
+
if (!fallbackText)
|
|
5814
|
+
fallbackText = "[Video]";
|
|
5815
|
+
} else if (msg.documentMessage) {
|
|
5816
|
+
const data = await this.downloadMediaSafe(message);
|
|
5817
|
+
if (data) {
|
|
5818
|
+
attachments.push({
|
|
5819
|
+
type: "document",
|
|
5820
|
+
mimeType: msg.documentMessage.mimetype ?? "application/octet-stream",
|
|
5821
|
+
fileName: msg.documentMessage.fileName ?? "document",
|
|
5822
|
+
size: msg.documentMessage.fileLength ?? data.length,
|
|
5823
|
+
data
|
|
5824
|
+
});
|
|
5825
|
+
}
|
|
5826
|
+
if (!fallbackText)
|
|
5827
|
+
fallbackText = "[Document]";
|
|
5828
|
+
} else if (msg.stickerMessage) {
|
|
5829
|
+
if (!text)
|
|
5830
|
+
return;
|
|
5831
|
+
}
|
|
5832
|
+
if (!fallbackText && attachments.length === 0)
|
|
5833
|
+
return;
|
|
5834
|
+
const normalized = {
|
|
5835
|
+
id: message.key.id ?? "",
|
|
5836
|
+
platform: "whatsapp",
|
|
5837
|
+
chatId: message.key.remoteJid ?? "",
|
|
5838
|
+
chatType: message.key.remoteJid?.endsWith("@g.us") ? "group" : "dm",
|
|
5839
|
+
userId: message.key.participant ?? message.key.remoteJid ?? "",
|
|
5840
|
+
userName: message.pushName ?? message.key.participant ?? message.key.remoteJid ?? "",
|
|
5841
|
+
text: fallbackText,
|
|
5842
|
+
timestamp: new Date(message.messageTimestamp * 1e3),
|
|
5843
|
+
replyToMessageId: msg.extendedTextMessage?.contextInfo?.stanzaId ?? void 0,
|
|
5844
|
+
attachments: attachments.length > 0 ? attachments : void 0
|
|
5845
|
+
};
|
|
5846
|
+
this.emit("message", normalized);
|
|
5847
|
+
}
|
|
5848
|
+
async downloadMediaSafe(message) {
|
|
5849
|
+
try {
|
|
5850
|
+
if (!this.downloadMedia)
|
|
5851
|
+
return void 0;
|
|
5852
|
+
const buffer = await this.downloadMedia(message, "buffer", {});
|
|
5853
|
+
return Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
|
|
5854
|
+
} catch {
|
|
5855
|
+
return void 0;
|
|
5856
|
+
}
|
|
5857
|
+
}
|
|
5858
|
+
guessMimeType(fileName) {
|
|
5859
|
+
const ext = fileName.split(".").pop()?.toLowerCase();
|
|
5860
|
+
const mimeMap = {
|
|
5861
|
+
pdf: "application/pdf",
|
|
5862
|
+
txt: "text/plain",
|
|
5863
|
+
json: "application/json",
|
|
5864
|
+
csv: "text/csv",
|
|
5865
|
+
png: "image/png",
|
|
5866
|
+
jpg: "image/jpeg",
|
|
5867
|
+
jpeg: "image/jpeg",
|
|
5868
|
+
gif: "image/gif",
|
|
5869
|
+
mp3: "audio/mpeg",
|
|
5870
|
+
ogg: "audio/ogg",
|
|
5871
|
+
mp4: "video/mp4",
|
|
5872
|
+
zip: "application/zip",
|
|
5873
|
+
doc: "application/msword",
|
|
5874
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
5875
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
5876
|
+
};
|
|
5877
|
+
return mimeMap[ext ?? ""] ?? "application/octet-stream";
|
|
5878
|
+
}
|
|
4502
5879
|
};
|
|
4503
5880
|
}
|
|
4504
5881
|
});
|
|
@@ -4593,10 +5970,24 @@ var init_signal = __esm({
|
|
|
4593
5970
|
const messages = await res.json();
|
|
4594
5971
|
for (const envelope of messages) {
|
|
4595
5972
|
const dataMessage = envelope.envelope?.dataMessage;
|
|
4596
|
-
if (!dataMessage
|
|
5973
|
+
if (!dataMessage)
|
|
5974
|
+
continue;
|
|
5975
|
+
if (!dataMessage.message && (!dataMessage.attachments || dataMessage.attachments.length === 0))
|
|
4597
5976
|
continue;
|
|
4598
5977
|
const data = envelope.envelope;
|
|
4599
5978
|
const chatId = dataMessage.groupInfo?.groupId ? `group.${dataMessage.groupInfo.groupId}` : data.sourceNumber ?? data.source ?? "";
|
|
5979
|
+
const attachments = [];
|
|
5980
|
+
if (dataMessage.attachments) {
|
|
5981
|
+
for (const att of dataMessage.attachments) {
|
|
5982
|
+
const downloaded = await this.downloadAttachment(att);
|
|
5983
|
+
if (downloaded) {
|
|
5984
|
+
attachments.push(downloaded);
|
|
5985
|
+
}
|
|
5986
|
+
}
|
|
5987
|
+
}
|
|
5988
|
+
const text = dataMessage.message || this.inferTextFromAttachments(attachments) || "";
|
|
5989
|
+
if (!text && attachments.length === 0)
|
|
5990
|
+
continue;
|
|
4600
5991
|
const normalized = {
|
|
4601
5992
|
id: String(dataMessage.timestamp ?? Date.now()),
|
|
4602
5993
|
platform: "signal",
|
|
@@ -4605,12 +5996,59 @@ var init_signal = __esm({
|
|
|
4605
5996
|
userId: data.sourceNumber ?? data.source ?? "",
|
|
4606
5997
|
userName: data.sourceName ?? data.sourceNumber ?? data.source ?? "",
|
|
4607
5998
|
displayName: data.sourceName,
|
|
4608
|
-
text
|
|
4609
|
-
timestamp: new Date(dataMessage.timestamp ?? Date.now())
|
|
5999
|
+
text,
|
|
6000
|
+
timestamp: new Date(dataMessage.timestamp ?? Date.now()),
|
|
6001
|
+
attachments: attachments.length > 0 ? attachments : void 0
|
|
4610
6002
|
};
|
|
4611
6003
|
this.emit("message", normalized);
|
|
4612
6004
|
}
|
|
4613
6005
|
}
|
|
6006
|
+
async downloadAttachment(att) {
|
|
6007
|
+
if (!att.id)
|
|
6008
|
+
return void 0;
|
|
6009
|
+
try {
|
|
6010
|
+
const res = await fetch(`${this.apiUrl}/v1/attachments/${att.id}`);
|
|
6011
|
+
if (!res.ok)
|
|
6012
|
+
return void 0;
|
|
6013
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
6014
|
+
const data = Buffer.from(arrayBuffer);
|
|
6015
|
+
const type = this.classifyContentType(att.contentType);
|
|
6016
|
+
return {
|
|
6017
|
+
type,
|
|
6018
|
+
mimeType: att.contentType ?? void 0,
|
|
6019
|
+
fileName: att.filename ?? void 0,
|
|
6020
|
+
size: att.size ?? data.length,
|
|
6021
|
+
data
|
|
6022
|
+
};
|
|
6023
|
+
} catch {
|
|
6024
|
+
return void 0;
|
|
6025
|
+
}
|
|
6026
|
+
}
|
|
6027
|
+
classifyContentType(contentType) {
|
|
6028
|
+
if (!contentType)
|
|
6029
|
+
return "other";
|
|
6030
|
+
if (contentType.startsWith("image/"))
|
|
6031
|
+
return "image";
|
|
6032
|
+
if (contentType.startsWith("audio/"))
|
|
6033
|
+
return "audio";
|
|
6034
|
+
if (contentType.startsWith("video/"))
|
|
6035
|
+
return "video";
|
|
6036
|
+
return "document";
|
|
6037
|
+
}
|
|
6038
|
+
inferTextFromAttachments(attachments) {
|
|
6039
|
+
if (attachments.length === 0)
|
|
6040
|
+
return "";
|
|
6041
|
+
const types = attachments.map((a) => a.type);
|
|
6042
|
+
if (types.includes("image"))
|
|
6043
|
+
return "[Photo]";
|
|
6044
|
+
if (types.includes("audio"))
|
|
6045
|
+
return "[Voice message]";
|
|
6046
|
+
if (types.includes("video"))
|
|
6047
|
+
return "[Video]";
|
|
6048
|
+
if (types.includes("document"))
|
|
6049
|
+
return "[Document]";
|
|
6050
|
+
return "[File]";
|
|
6051
|
+
}
|
|
4614
6052
|
};
|
|
4615
6053
|
}
|
|
4616
6054
|
});
|
|
@@ -4638,8 +6076,8 @@ var init_dist7 = __esm({
|
|
|
4638
6076
|
});
|
|
4639
6077
|
|
|
4640
6078
|
// ../core/dist/alfred.js
|
|
4641
|
-
import
|
|
4642
|
-
import
|
|
6079
|
+
import fs6 from "node:fs";
|
|
6080
|
+
import path8 from "node:path";
|
|
4643
6081
|
import yaml2 from "js-yaml";
|
|
4644
6082
|
var Alfred;
|
|
4645
6083
|
var init_alfred = __esm({
|
|
@@ -4653,6 +6091,7 @@ var init_alfred = __esm({
|
|
|
4653
6091
|
init_conversation_manager();
|
|
4654
6092
|
init_message_pipeline();
|
|
4655
6093
|
init_reminder_scheduler();
|
|
6094
|
+
init_speech_transcriber();
|
|
4656
6095
|
Alfred = class {
|
|
4657
6096
|
config;
|
|
4658
6097
|
logger;
|
|
@@ -4673,6 +6112,7 @@ var init_alfred = __esm({
|
|
|
4673
6112
|
const auditRepo = new AuditRepository(db);
|
|
4674
6113
|
const memoryRepo = new MemoryRepository(db);
|
|
4675
6114
|
const reminderRepo = new ReminderRepository(db);
|
|
6115
|
+
const noteRepo = new NoteRepository(db);
|
|
4676
6116
|
this.logger.info("Storage initialized");
|
|
4677
6117
|
const ruleEngine = new RuleEngine();
|
|
4678
6118
|
const rules = this.loadSecurityRules();
|
|
@@ -4682,6 +6122,7 @@ var init_alfred = __esm({
|
|
|
4682
6122
|
const llmProvider = createLLMProvider(this.config.llm);
|
|
4683
6123
|
await llmProvider.initialize();
|
|
4684
6124
|
this.logger.info({ provider: this.config.llm.provider, model: this.config.llm.model }, "LLM provider initialized");
|
|
6125
|
+
const skillSandbox = new SkillSandbox(this.logger.child({ component: "sandbox" }));
|
|
4685
6126
|
const skillRegistry = new SkillRegistry();
|
|
4686
6127
|
skillRegistry.register(new CalculatorSkill());
|
|
4687
6128
|
skillRegistry.register(new SystemInfoSkill());
|
|
@@ -4691,22 +6132,30 @@ var init_alfred = __esm({
|
|
|
4691
6132
|
baseUrl: this.config.search.baseUrl
|
|
4692
6133
|
} : void 0));
|
|
4693
6134
|
skillRegistry.register(new ReminderSkill(reminderRepo));
|
|
4694
|
-
skillRegistry.register(new NoteSkill());
|
|
4695
|
-
skillRegistry.register(new SummarizeSkill());
|
|
4696
|
-
skillRegistry.register(new TranslateSkill());
|
|
6135
|
+
skillRegistry.register(new NoteSkill(noteRepo));
|
|
4697
6136
|
skillRegistry.register(new WeatherSkill());
|
|
4698
6137
|
skillRegistry.register(new ShellSkill());
|
|
4699
6138
|
skillRegistry.register(new MemorySkill(memoryRepo));
|
|
4700
|
-
skillRegistry.register(new DelegateSkill(llmProvider));
|
|
6139
|
+
skillRegistry.register(new DelegateSkill(llmProvider, skillRegistry, skillSandbox, securityManager));
|
|
4701
6140
|
skillRegistry.register(new EmailSkill(this.config.email ? {
|
|
4702
6141
|
imap: this.config.email.imap,
|
|
4703
6142
|
smtp: this.config.email.smtp,
|
|
4704
6143
|
auth: this.config.email.auth
|
|
4705
6144
|
} : void 0));
|
|
6145
|
+
skillRegistry.register(new HttpSkill());
|
|
6146
|
+
skillRegistry.register(new FileSkill());
|
|
6147
|
+
skillRegistry.register(new ClipboardSkill());
|
|
6148
|
+
skillRegistry.register(new ScreenshotSkill());
|
|
6149
|
+
skillRegistry.register(new BrowserSkill());
|
|
4706
6150
|
this.logger.info({ skills: skillRegistry.getAll().map((s) => s.metadata.name) }, "Skills registered");
|
|
4707
|
-
|
|
6151
|
+
let speechTranscriber;
|
|
6152
|
+
if (this.config.speech?.apiKey) {
|
|
6153
|
+
speechTranscriber = new SpeechTranscriber(this.config.speech, this.logger.child({ component: "speech" }));
|
|
6154
|
+
this.logger.info({ provider: this.config.speech.provider }, "Speech-to-text initialized");
|
|
6155
|
+
}
|
|
4708
6156
|
const conversationManager = new ConversationManager(conversationRepo);
|
|
4709
|
-
|
|
6157
|
+
const inboxPath = path8.resolve(path8.dirname(this.config.storage.path), "inbox");
|
|
6158
|
+
this.pipeline = new MessagePipeline(llmProvider, conversationManager, userRepo, this.logger.child({ component: "pipeline" }), skillRegistry, skillSandbox, securityManager, memoryRepo, speechTranscriber, inboxPath);
|
|
4710
6159
|
this.reminderScheduler = new ReminderScheduler(reminderRepo, async (platform, chatId, text) => {
|
|
4711
6160
|
const adapter = this.adapters.get(platform);
|
|
4712
6161
|
if (adapter) {
|
|
@@ -4821,22 +6270,22 @@ var init_alfred = __esm({
|
|
|
4821
6270
|
});
|
|
4822
6271
|
}
|
|
4823
6272
|
loadSecurityRules() {
|
|
4824
|
-
const rulesPath =
|
|
6273
|
+
const rulesPath = path8.resolve(this.config.security.rulesPath);
|
|
4825
6274
|
const rules = [];
|
|
4826
|
-
if (!
|
|
6275
|
+
if (!fs6.existsSync(rulesPath)) {
|
|
4827
6276
|
this.logger.warn({ rulesPath }, "Security rules directory not found, using default deny");
|
|
4828
6277
|
return rules;
|
|
4829
6278
|
}
|
|
4830
|
-
const stat =
|
|
6279
|
+
const stat = fs6.statSync(rulesPath);
|
|
4831
6280
|
if (!stat.isDirectory()) {
|
|
4832
6281
|
this.logger.warn({ rulesPath }, "Security rules path is not a directory");
|
|
4833
6282
|
return rules;
|
|
4834
6283
|
}
|
|
4835
|
-
const files =
|
|
6284
|
+
const files = fs6.readdirSync(rulesPath).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
4836
6285
|
for (const file of files) {
|
|
4837
6286
|
try {
|
|
4838
|
-
const filePath =
|
|
4839
|
-
const content =
|
|
6287
|
+
const filePath = path8.join(rulesPath, file);
|
|
6288
|
+
const content = fs6.readFileSync(filePath, "utf-8");
|
|
4840
6289
|
const parsed = yaml2.load(content);
|
|
4841
6290
|
if (parsed?.rules && Array.isArray(parsed.rules)) {
|
|
4842
6291
|
rules.push(...parsed.rules);
|
|
@@ -4860,6 +6309,7 @@ var init_dist8 = __esm({
|
|
|
4860
6309
|
init_message_pipeline();
|
|
4861
6310
|
init_conversation_manager();
|
|
4862
6311
|
init_reminder_scheduler();
|
|
6312
|
+
init_speech_transcriber();
|
|
4863
6313
|
}
|
|
4864
6314
|
});
|
|
4865
6315
|
|
|
@@ -4930,8 +6380,8 @@ __export(setup_exports, {
|
|
|
4930
6380
|
});
|
|
4931
6381
|
import { createInterface } from "node:readline/promises";
|
|
4932
6382
|
import { stdin as input, stdout as output } from "node:process";
|
|
4933
|
-
import
|
|
4934
|
-
import
|
|
6383
|
+
import fs7 from "node:fs";
|
|
6384
|
+
import path9 from "node:path";
|
|
4935
6385
|
import yaml3 from "js-yaml";
|
|
4936
6386
|
function green(s) {
|
|
4937
6387
|
return `${GREEN}${s}${RESET}`;
|
|
@@ -4962,20 +6412,20 @@ function loadExistingConfig(projectRoot) {
|
|
|
4962
6412
|
let shellEnabled = false;
|
|
4963
6413
|
let writeInGroups = false;
|
|
4964
6414
|
let rateLimit = 30;
|
|
4965
|
-
const configPath =
|
|
4966
|
-
if (
|
|
6415
|
+
const configPath = path9.join(projectRoot, "config", "default.yml");
|
|
6416
|
+
if (fs7.existsSync(configPath)) {
|
|
4967
6417
|
try {
|
|
4968
|
-
const parsed = yaml3.load(
|
|
6418
|
+
const parsed = yaml3.load(fs7.readFileSync(configPath, "utf-8"));
|
|
4969
6419
|
if (parsed && typeof parsed === "object") {
|
|
4970
6420
|
Object.assign(config, parsed);
|
|
4971
6421
|
}
|
|
4972
6422
|
} catch {
|
|
4973
6423
|
}
|
|
4974
6424
|
}
|
|
4975
|
-
const envPath =
|
|
4976
|
-
if (
|
|
6425
|
+
const envPath = path9.join(projectRoot, ".env");
|
|
6426
|
+
if (fs7.existsSync(envPath)) {
|
|
4977
6427
|
try {
|
|
4978
|
-
const lines =
|
|
6428
|
+
const lines = fs7.readFileSync(envPath, "utf-8").split("\n");
|
|
4979
6429
|
for (const line of lines) {
|
|
4980
6430
|
const trimmed = line.trim();
|
|
4981
6431
|
if (!trimmed || trimmed.startsWith("#"))
|
|
@@ -4988,10 +6438,10 @@ function loadExistingConfig(projectRoot) {
|
|
|
4988
6438
|
} catch {
|
|
4989
6439
|
}
|
|
4990
6440
|
}
|
|
4991
|
-
const rulesPath =
|
|
4992
|
-
if (
|
|
6441
|
+
const rulesPath = path9.join(projectRoot, "config", "rules", "default-rules.yml");
|
|
6442
|
+
if (fs7.existsSync(rulesPath)) {
|
|
4993
6443
|
try {
|
|
4994
|
-
const rulesContent = yaml3.load(
|
|
6444
|
+
const rulesContent = yaml3.load(fs7.readFileSync(rulesPath, "utf-8"));
|
|
4995
6445
|
if (rulesContent?.rules) {
|
|
4996
6446
|
shellEnabled = rulesContent.rules.some((r) => r.id === "allow-owner-admin" && r.effect === "allow");
|
|
4997
6447
|
const writeDmRule = rulesContent.rules.find((r) => r.id === "allow-write-for-dm" || r.id === "allow-write-all");
|
|
@@ -5247,6 +6697,54 @@ ${bold("Email access (read & send emails via IMAP/SMTP)?")}`);
|
|
|
5247
6697
|
} else {
|
|
5248
6698
|
console.log(` ${dim("Email disabled \u2014 you can configure it later.")}`);
|
|
5249
6699
|
}
|
|
6700
|
+
const speechProviders = ["openai", "groq"];
|
|
6701
|
+
const existingSpeechProvider = existing.config.speech?.provider ?? existing.env["ALFRED_SPEECH_PROVIDER"] ?? "";
|
|
6702
|
+
const existingSpeechIdx = speechProviders.indexOf(existingSpeechProvider);
|
|
6703
|
+
const defaultSpeechChoice = existingSpeechIdx >= 0 ? existingSpeechIdx + 1 : 0;
|
|
6704
|
+
console.log(`
|
|
6705
|
+
${bold("Voice message transcription (Speech-to-Text via Whisper)?")}`);
|
|
6706
|
+
console.log(`${dim("Transcribes voice messages from Telegram, Discord, etc.")}`);
|
|
6707
|
+
const speechLabels = [
|
|
6708
|
+
"OpenAI Whisper \u2014 best quality",
|
|
6709
|
+
"Groq Whisper \u2014 fast & free"
|
|
6710
|
+
];
|
|
6711
|
+
console.log(` ${cyan("0)")} None (disable voice transcription)${existingSpeechIdx === -1 ? ` ${dim("(current)")}` : ""}`);
|
|
6712
|
+
for (let i = 0; i < speechLabels.length; i++) {
|
|
6713
|
+
const cur = existingSpeechIdx === i ? ` ${dim("(current)")}` : "";
|
|
6714
|
+
console.log(` ${cyan(String(i + 1) + ")")} ${speechLabels[i]}${cur}`);
|
|
6715
|
+
}
|
|
6716
|
+
const speechChoice = await askNumber(rl, "> ", 0, speechProviders.length, defaultSpeechChoice);
|
|
6717
|
+
let speechProvider;
|
|
6718
|
+
let speechApiKey = "";
|
|
6719
|
+
let speechBaseUrl = "";
|
|
6720
|
+
if (speechChoice >= 1 && speechChoice <= speechProviders.length) {
|
|
6721
|
+
speechProvider = speechProviders[speechChoice - 1];
|
|
6722
|
+
}
|
|
6723
|
+
if (speechProvider === "openai") {
|
|
6724
|
+
const existingKey = existing.env["ALFRED_SPEECH_API_KEY"] ?? "";
|
|
6725
|
+
if (existingKey) {
|
|
6726
|
+
speechApiKey = await askWithDefault(rl, " OpenAI API key (for Whisper)", existingKey);
|
|
6727
|
+
} else {
|
|
6728
|
+
console.log(` ${dim("Uses your OpenAI API key for Whisper transcription.")}`);
|
|
6729
|
+
speechApiKey = await askRequired(rl, " OpenAI API key");
|
|
6730
|
+
}
|
|
6731
|
+
console.log(` ${green(">")} OpenAI Whisper: ${dim(maskKey(speechApiKey))}`);
|
|
6732
|
+
} else if (speechProvider === "groq") {
|
|
6733
|
+
const existingKey = existing.env["ALFRED_SPEECH_API_KEY"] ?? "";
|
|
6734
|
+
if (existingKey) {
|
|
6735
|
+
speechApiKey = await askWithDefault(rl, " Groq API key", existingKey);
|
|
6736
|
+
} else {
|
|
6737
|
+
console.log(` ${dim("Get your free API key at: https://console.groq.com/")}`);
|
|
6738
|
+
speechApiKey = await askRequired(rl, " Groq API key");
|
|
6739
|
+
}
|
|
6740
|
+
const existingUrl = existing.env["ALFRED_SPEECH_BASE_URL"] ?? "";
|
|
6741
|
+
if (existingUrl) {
|
|
6742
|
+
speechBaseUrl = await askWithDefault(rl, " Groq API URL", existingUrl);
|
|
6743
|
+
}
|
|
6744
|
+
console.log(` ${green(">")} Groq Whisper: ${dim(maskKey(speechApiKey))}`);
|
|
6745
|
+
} else {
|
|
6746
|
+
console.log(` ${dim("Voice transcription disabled \u2014 you can configure it later.")}`);
|
|
6747
|
+
}
|
|
5250
6748
|
console.log(`
|
|
5251
6749
|
${bold("Security configuration:")}`);
|
|
5252
6750
|
const existingOwnerId = existing.config.security?.ownerUserId ?? existing.env["ALFRED_OWNER_USER_ID"] ?? "";
|
|
@@ -5342,6 +6840,17 @@ ${bold("Writing configuration files...")}`);
|
|
|
5342
6840
|
envLines.push("# ALFRED_EMAIL_USER=");
|
|
5343
6841
|
envLines.push("# ALFRED_EMAIL_PASS=");
|
|
5344
6842
|
}
|
|
6843
|
+
envLines.push("", "# === Speech-to-Text ===", "");
|
|
6844
|
+
if (speechProvider) {
|
|
6845
|
+
envLines.push(`ALFRED_SPEECH_PROVIDER=${speechProvider}`);
|
|
6846
|
+
envLines.push(`ALFRED_SPEECH_API_KEY=${speechApiKey}`);
|
|
6847
|
+
if (speechBaseUrl) {
|
|
6848
|
+
envLines.push(`ALFRED_SPEECH_BASE_URL=${speechBaseUrl}`);
|
|
6849
|
+
}
|
|
6850
|
+
} else {
|
|
6851
|
+
envLines.push("# ALFRED_SPEECH_PROVIDER=groq");
|
|
6852
|
+
envLines.push("# ALFRED_SPEECH_API_KEY=");
|
|
6853
|
+
}
|
|
5345
6854
|
envLines.push("", "# === Security ===", "");
|
|
5346
6855
|
if (ownerUserId) {
|
|
5347
6856
|
envLines.push(`ALFRED_OWNER_USER_ID=${ownerUserId}`);
|
|
@@ -5349,12 +6858,12 @@ ${bold("Writing configuration files...")}`);
|
|
|
5349
6858
|
envLines.push("# ALFRED_OWNER_USER_ID=");
|
|
5350
6859
|
}
|
|
5351
6860
|
envLines.push("");
|
|
5352
|
-
const envPath =
|
|
5353
|
-
|
|
6861
|
+
const envPath = path9.join(projectRoot, ".env");
|
|
6862
|
+
fs7.writeFileSync(envPath, envLines.join("\n"), "utf-8");
|
|
5354
6863
|
console.log(` ${green("+")} ${dim(".env")} written`);
|
|
5355
|
-
const configDir =
|
|
5356
|
-
if (!
|
|
5357
|
-
|
|
6864
|
+
const configDir = path9.join(projectRoot, "config");
|
|
6865
|
+
if (!fs7.existsSync(configDir)) {
|
|
6866
|
+
fs7.mkdirSync(configDir, { recursive: true });
|
|
5358
6867
|
}
|
|
5359
6868
|
const config = {
|
|
5360
6869
|
name: botName,
|
|
@@ -5402,6 +6911,13 @@ ${bold("Writing configuration files...")}`);
|
|
|
5402
6911
|
auth: { user: emailUser, pass: emailPass }
|
|
5403
6912
|
}
|
|
5404
6913
|
} : {},
|
|
6914
|
+
...speechProvider ? {
|
|
6915
|
+
speech: {
|
|
6916
|
+
provider: speechProvider,
|
|
6917
|
+
apiKey: speechApiKey,
|
|
6918
|
+
...speechBaseUrl ? { baseUrl: speechBaseUrl } : {}
|
|
6919
|
+
}
|
|
6920
|
+
} : {},
|
|
5405
6921
|
storage: {
|
|
5406
6922
|
path: "./data/alfred.db"
|
|
5407
6923
|
},
|
|
@@ -5419,12 +6935,12 @@ ${bold("Writing configuration files...")}`);
|
|
|
5419
6935
|
config.security.ownerUserId = ownerUserId;
|
|
5420
6936
|
}
|
|
5421
6937
|
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 });
|
|
5422
|
-
const configPath =
|
|
5423
|
-
|
|
6938
|
+
const configPath = path9.join(configDir, "default.yml");
|
|
6939
|
+
fs7.writeFileSync(configPath, yamlStr, "utf-8");
|
|
5424
6940
|
console.log(` ${green("+")} ${dim("config/default.yml")} written`);
|
|
5425
|
-
const rulesDir =
|
|
5426
|
-
if (!
|
|
5427
|
-
|
|
6941
|
+
const rulesDir = path9.join(configDir, "rules");
|
|
6942
|
+
if (!fs7.existsSync(rulesDir)) {
|
|
6943
|
+
fs7.mkdirSync(rulesDir, { recursive: true });
|
|
5428
6944
|
}
|
|
5429
6945
|
const ownerAdminRule = enableShell && ownerUserId ? `
|
|
5430
6946
|
# Allow admin actions (shell, etc.) for the owner only
|
|
@@ -5505,12 +7021,12 @@ ${ownerAdminRule}
|
|
|
5505
7021
|
actions: ["*"]
|
|
5506
7022
|
riskLevels: [read, write, destructive, admin]
|
|
5507
7023
|
`;
|
|
5508
|
-
const rulesPath =
|
|
5509
|
-
|
|
7024
|
+
const rulesPath = path9.join(rulesDir, "default-rules.yml");
|
|
7025
|
+
fs7.writeFileSync(rulesPath, rulesYaml, "utf-8");
|
|
5510
7026
|
console.log(` ${green("+")} ${dim("config/rules/default-rules.yml")} written`);
|
|
5511
|
-
const dataDir =
|
|
5512
|
-
if (!
|
|
5513
|
-
|
|
7027
|
+
const dataDir = path9.join(projectRoot, "data");
|
|
7028
|
+
if (!fs7.existsSync(dataDir)) {
|
|
7029
|
+
fs7.mkdirSync(dataDir, { recursive: true });
|
|
5514
7030
|
console.log(` ${green("+")} ${dim("data/")} directory created`);
|
|
5515
7031
|
}
|
|
5516
7032
|
console.log("");
|
|
@@ -5544,6 +7060,15 @@ ${ownerAdminRule}
|
|
|
5544
7060
|
} else {
|
|
5545
7061
|
console.log(` ${bold("Email:")} ${dim("disabled")}`);
|
|
5546
7062
|
}
|
|
7063
|
+
if (speechProvider) {
|
|
7064
|
+
const speechLabelMap = {
|
|
7065
|
+
openai: "OpenAI Whisper",
|
|
7066
|
+
groq: "Groq Whisper"
|
|
7067
|
+
};
|
|
7068
|
+
console.log(` ${bold("Voice:")} ${speechLabelMap[speechProvider]}`);
|
|
7069
|
+
} else {
|
|
7070
|
+
console.log(` ${bold("Voice:")} ${dim("disabled")}`);
|
|
7071
|
+
}
|
|
5547
7072
|
if (ownerUserId) {
|
|
5548
7073
|
console.log(` ${bold("Owner ID:")} ${ownerUserId}`);
|
|
5549
7074
|
console.log(` ${bold("Shell access:")} ${enableShell ? green("enabled") : dim("disabled")}`);
|
|
@@ -5783,8 +7308,8 @@ var rules_exports = {};
|
|
|
5783
7308
|
__export(rules_exports, {
|
|
5784
7309
|
rulesCommand: () => rulesCommand
|
|
5785
7310
|
});
|
|
5786
|
-
import
|
|
5787
|
-
import
|
|
7311
|
+
import fs8 from "node:fs";
|
|
7312
|
+
import path10 from "node:path";
|
|
5788
7313
|
import yaml4 from "js-yaml";
|
|
5789
7314
|
async function rulesCommand() {
|
|
5790
7315
|
const configLoader = new ConfigLoader();
|
|
@@ -5795,18 +7320,18 @@ async function rulesCommand() {
|
|
|
5795
7320
|
console.error("Failed to load configuration:", error.message);
|
|
5796
7321
|
process.exit(1);
|
|
5797
7322
|
}
|
|
5798
|
-
const rulesPath =
|
|
5799
|
-
if (!
|
|
7323
|
+
const rulesPath = path10.resolve(config.security.rulesPath);
|
|
7324
|
+
if (!fs8.existsSync(rulesPath)) {
|
|
5800
7325
|
console.log(`Rules directory not found: ${rulesPath}`);
|
|
5801
7326
|
console.log("No security rules loaded.");
|
|
5802
7327
|
return;
|
|
5803
7328
|
}
|
|
5804
|
-
const stat =
|
|
7329
|
+
const stat = fs8.statSync(rulesPath);
|
|
5805
7330
|
if (!stat.isDirectory()) {
|
|
5806
7331
|
console.error(`Rules path is not a directory: ${rulesPath}`);
|
|
5807
7332
|
process.exit(1);
|
|
5808
7333
|
}
|
|
5809
|
-
const files =
|
|
7334
|
+
const files = fs8.readdirSync(rulesPath).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
5810
7335
|
if (files.length === 0) {
|
|
5811
7336
|
console.log(`No YAML rule files found in: ${rulesPath}`);
|
|
5812
7337
|
return;
|
|
@@ -5815,9 +7340,9 @@ async function rulesCommand() {
|
|
|
5815
7340
|
const allRules = [];
|
|
5816
7341
|
const errors = [];
|
|
5817
7342
|
for (const file of files) {
|
|
5818
|
-
const filePath =
|
|
7343
|
+
const filePath = path10.join(rulesPath, file);
|
|
5819
7344
|
try {
|
|
5820
|
-
const raw =
|
|
7345
|
+
const raw = fs8.readFileSync(filePath, "utf-8");
|
|
5821
7346
|
const parsed = yaml4.load(raw);
|
|
5822
7347
|
const rules = ruleLoader.loadFromObject(parsed);
|
|
5823
7348
|
allRules.push(...rules);
|
|
@@ -5869,8 +7394,8 @@ var status_exports = {};
|
|
|
5869
7394
|
__export(status_exports, {
|
|
5870
7395
|
statusCommand: () => statusCommand
|
|
5871
7396
|
});
|
|
5872
|
-
import
|
|
5873
|
-
import
|
|
7397
|
+
import fs9 from "node:fs";
|
|
7398
|
+
import path11 from "node:path";
|
|
5874
7399
|
import yaml5 from "js-yaml";
|
|
5875
7400
|
async function statusCommand() {
|
|
5876
7401
|
const configLoader = new ConfigLoader();
|
|
@@ -5927,22 +7452,22 @@ async function statusCommand() {
|
|
|
5927
7452
|
}
|
|
5928
7453
|
console.log("");
|
|
5929
7454
|
console.log("Storage:");
|
|
5930
|
-
const dbPath =
|
|
5931
|
-
const dbExists =
|
|
7455
|
+
const dbPath = path11.resolve(config.storage.path);
|
|
7456
|
+
const dbExists = fs9.existsSync(dbPath);
|
|
5932
7457
|
console.log(` Database: ${dbPath}`);
|
|
5933
7458
|
console.log(` Status: ${dbExists ? "exists" : "not yet created"}`);
|
|
5934
7459
|
console.log("");
|
|
5935
|
-
const rulesPath =
|
|
7460
|
+
const rulesPath = path11.resolve(config.security.rulesPath);
|
|
5936
7461
|
let ruleCount = 0;
|
|
5937
7462
|
let ruleFileCount = 0;
|
|
5938
|
-
if (
|
|
5939
|
-
const files =
|
|
7463
|
+
if (fs9.existsSync(rulesPath) && fs9.statSync(rulesPath).isDirectory()) {
|
|
7464
|
+
const files = fs9.readdirSync(rulesPath).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
5940
7465
|
ruleFileCount = files.length;
|
|
5941
7466
|
const ruleLoader = new RuleLoader();
|
|
5942
7467
|
for (const file of files) {
|
|
5943
|
-
const filePath =
|
|
7468
|
+
const filePath = path11.join(rulesPath, file);
|
|
5944
7469
|
try {
|
|
5945
|
-
const raw =
|
|
7470
|
+
const raw = fs9.readFileSync(filePath, "utf-8");
|
|
5946
7471
|
const parsed = yaml5.load(raw);
|
|
5947
7472
|
const rules = ruleLoader.loadFromObject(parsed);
|
|
5948
7473
|
ruleCount += rules.length;
|
|
@@ -5976,8 +7501,8 @@ var logs_exports = {};
|
|
|
5976
7501
|
__export(logs_exports, {
|
|
5977
7502
|
logsCommand: () => logsCommand
|
|
5978
7503
|
});
|
|
5979
|
-
import
|
|
5980
|
-
import
|
|
7504
|
+
import fs10 from "node:fs";
|
|
7505
|
+
import path12 from "node:path";
|
|
5981
7506
|
async function logsCommand(tail) {
|
|
5982
7507
|
const configLoader = new ConfigLoader();
|
|
5983
7508
|
let config;
|
|
@@ -5987,8 +7512,8 @@ async function logsCommand(tail) {
|
|
|
5987
7512
|
console.error("Failed to load configuration:", error.message);
|
|
5988
7513
|
process.exit(1);
|
|
5989
7514
|
}
|
|
5990
|
-
const dbPath =
|
|
5991
|
-
if (!
|
|
7515
|
+
const dbPath = path12.resolve(config.storage.path);
|
|
7516
|
+
if (!fs10.existsSync(dbPath)) {
|
|
5992
7517
|
console.log(`Database not found at: ${dbPath}`);
|
|
5993
7518
|
console.log("No audit log entries. Alfred has not been run yet, or the database path is incorrect.");
|
|
5994
7519
|
return;
|