alvin-bot 4.4.1
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/.env.example +43 -0
- package/BACKLOG.md +223 -0
- package/CHANGELOG.md +63 -0
- package/CLAUDE.example.md +152 -0
- package/CODE_OF_CONDUCT.md +52 -0
- package/CONTRIBUTING.md +72 -0
- package/LICENSE +21 -0
- package/README.md +529 -0
- package/SECURITY.md +38 -0
- package/SOUL.example.md +60 -0
- package/TOOLS.example.md +42 -0
- package/alvin-bot.config.example.json +24 -0
- package/bin/cli.js +1088 -0
- package/dist/.metadata_never_index +0 -0
- package/dist/claude.js +102 -0
- package/dist/config.js +65 -0
- package/dist/engine.js +90 -0
- package/dist/find-claude-binary.js +98 -0
- package/dist/handlers/commands.js +1489 -0
- package/dist/handlers/document.js +187 -0
- package/dist/handlers/message.js +200 -0
- package/dist/handlers/photo.js +154 -0
- package/dist/handlers/platform-message.js +275 -0
- package/dist/handlers/video.js +237 -0
- package/dist/handlers/voice.js +148 -0
- package/dist/i18n.js +299 -0
- package/dist/index.js +442 -0
- package/dist/init-data-dir.js +81 -0
- package/dist/middleware/auth.js +215 -0
- package/dist/migrate.js +139 -0
- package/dist/paths.js +87 -0
- package/dist/platforms/discord.js +161 -0
- package/dist/platforms/index.js +130 -0
- package/dist/platforms/signal.js +205 -0
- package/dist/platforms/slack.js +318 -0
- package/dist/platforms/telegram.js +111 -0
- package/dist/platforms/types.js +8 -0
- package/dist/platforms/whatsapp.js +648 -0
- package/dist/providers/claude-sdk-provider.js +173 -0
- package/dist/providers/codex-cli-provider.js +121 -0
- package/dist/providers/index.js +7 -0
- package/dist/providers/openai-compatible.js +388 -0
- package/dist/providers/registry.js +209 -0
- package/dist/providers/tool-executor.js +450 -0
- package/dist/providers/types.js +205 -0
- package/dist/services/access.js +144 -0
- package/dist/services/asset-index.js +230 -0
- package/dist/services/browser-manager.js +161 -0
- package/dist/services/browser.js +121 -0
- package/dist/services/compaction.js +129 -0
- package/dist/services/cron.js +462 -0
- package/dist/services/custom-tools.js +317 -0
- package/dist/services/delivery-queue.js +154 -0
- package/dist/services/elevenlabs.js +58 -0
- package/dist/services/embeddings.js +386 -0
- package/dist/services/exec-guard.js +46 -0
- package/dist/services/fallback-order.js +151 -0
- package/dist/services/heartbeat.js +192 -0
- package/dist/services/hooks.js +44 -0
- package/dist/services/imagegen.js +72 -0
- package/dist/services/language-detect.js +144 -0
- package/dist/services/markdown.js +63 -0
- package/dist/services/mcp.js +252 -0
- package/dist/services/memory.js +133 -0
- package/dist/services/personality.js +227 -0
- package/dist/services/plugins.js +171 -0
- package/dist/services/reminders.js +97 -0
- package/dist/services/restart.js +48 -0
- package/dist/services/security-audit.js +66 -0
- package/dist/services/self-search.js +129 -0
- package/dist/services/session.js +93 -0
- package/dist/services/skills.js +287 -0
- package/dist/services/standing-orders.js +29 -0
- package/dist/services/subagents.js +142 -0
- package/dist/services/sudo.js +243 -0
- package/dist/services/telegram.js +113 -0
- package/dist/services/tool-discovery.js +214 -0
- package/dist/services/usage-tracker.js +137 -0
- package/dist/services/users.js +199 -0
- package/dist/services/voice.js +95 -0
- package/dist/tui/index.js +507 -0
- package/dist/web/canvas.js +30 -0
- package/dist/web/doctor-api.js +606 -0
- package/dist/web/openai-compat.js +252 -0
- package/dist/web/server.js +1351 -0
- package/dist/web/setup-api.js +1078 -0
- package/docs/mcp.example.json +16 -0
- package/docs/screenshots/00-Login.png +0 -0
- package/docs/screenshots/01-Chat-Dark-Conversation.png +0 -0
- package/docs/screenshots/02-Chat.png +0 -0
- package/docs/screenshots/03-Dashboard-Overview.png +0 -0
- package/docs/screenshots/04-AI-Models-and-Providers.png +0 -0
- package/docs/screenshots/05-Personality-Editor.png +0 -0
- package/docs/screenshots/06-Memory-Manager.png +0 -0
- package/docs/screenshots/07-Active-Sessions.png +0 -0
- package/docs/screenshots/08-File-Browser.png +0 -0
- package/docs/screenshots/09-Scheduled-Jobs.png +0 -0
- package/docs/screenshots/10-Custom-Tools.png +0 -0
- package/docs/screenshots/11-Plugins-and-MCP.png +0 -0
- package/docs/screenshots/12-Messaging-Platforms.png +0 -0
- package/docs/screenshots/12.1-Messaging-Platforms-WhatsApp-Groups-List.png +0 -0
- package/docs/screenshots/12.2-Messaging-Platforms-WA-Group-Details.png +0 -0
- package/docs/screenshots/13-User-Management.png +0 -0
- package/docs/screenshots/14-Web-Terminal.png +0 -0
- package/docs/screenshots/15-Maintenance-and-Health.png +0 -0
- package/docs/screenshots/16-Settings-and-Env.png +0 -0
- package/docs/screenshots/TG-commands.png +0 -0
- package/docs/screenshots/TG.png +0 -0
- package/docs/screenshots/_Mac-Installer.png +0 -0
- package/docs/tools.example.json +33 -0
- package/install.sh +165 -0
- package/package.json +190 -0
- package/plugins/calendar/index.js +270 -0
- package/plugins/email/index.js +231 -0
- package/plugins/finance/index.js +254 -0
- package/plugins/notes/index.js +227 -0
- package/plugins/smarthome/index.js +230 -0
- package/plugins/weather/index.js +122 -0
- package/skills/apple-notes/SKILL.md +31 -0
- package/skills/browse/SKILL.md +136 -0
- package/skills/code-project/SKILL.md +43 -0
- package/skills/data-analysis/SKILL.md +39 -0
- package/skills/document-creation/SKILL.md +48 -0
- package/skills/email-summary/SKILL.md +46 -0
- package/skills/github/SKILL.md +42 -0
- package/skills/summarize/SKILL.md +28 -0
- package/skills/system-admin/SKILL.md +39 -0
- package/skills/weather/SKILL.md +34 -0
- package/skills/web-research/SKILL.md +35 -0
- package/web/public/canvas.html +52 -0
- package/web/public/css/style.css +555 -0
- package/web/public/index.html +189 -0
- package/web/public/js/app.js +3102 -0
- package/web/public/js/i18n.js +1048 -0
- package/web/public/js/icons.js +104 -0
- package/web/public/login.html +48 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar Plugin — View and manage calendar events.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - Google Calendar (via API, needs GOOGLE_CALENDAR_API_KEY or OAuth)
|
|
6
|
+
* - iCal URL import (any .ics feed)
|
|
7
|
+
* - Local event storage (docs/calendar.json) as fallback
|
|
8
|
+
*
|
|
9
|
+
* For Google Calendar: set GOOGLE_CALENDAR_ID and GOOGLE_API_KEY in .env
|
|
10
|
+
* For iCal: set ICAL_URL in .env
|
|
11
|
+
* Without either: uses local JSON storage
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from "fs";
|
|
15
|
+
import path from "path";
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
17
|
+
|
|
18
|
+
const PLUGIN_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
19
|
+
const CALENDAR_FILE = path.resolve(PLUGIN_ROOT, "docs", "calendar.json");
|
|
20
|
+
|
|
21
|
+
// ── Local Event Storage ─────────────────────────────────
|
|
22
|
+
|
|
23
|
+
function loadEvents() {
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(fs.readFileSync(CALENDAR_FILE, "utf-8"));
|
|
26
|
+
} catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function saveEvents(events) {
|
|
32
|
+
fs.writeFileSync(CALENDAR_FILE, JSON.stringify(events, null, 2));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatDate(date) {
|
|
36
|
+
return new Date(date).toLocaleDateString("de-DE", {
|
|
37
|
+
weekday: "short", day: "2-digit", month: "2-digit", year: "numeric",
|
|
38
|
+
hour: "2-digit", minute: "2-digit",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseDateTime(input) {
|
|
43
|
+
// Try common formats
|
|
44
|
+
// "morgen 14:00" "2026-03-01 09:00" "in 2h" etc.
|
|
45
|
+
const now = new Date();
|
|
46
|
+
|
|
47
|
+
if (input.startsWith("in ")) {
|
|
48
|
+
const match = input.match(/in (\d+)\s*(m|min|h|std|d|tag)/i);
|
|
49
|
+
if (match) {
|
|
50
|
+
const amount = parseInt(match[1]);
|
|
51
|
+
const unit = match[2].toLowerCase();
|
|
52
|
+
const ms = unit.startsWith("m") ? amount * 60000 :
|
|
53
|
+
unit.startsWith("h") || unit.startsWith("s") ? amount * 3600000 :
|
|
54
|
+
amount * 86400000;
|
|
55
|
+
return new Date(now.getTime() + ms);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (input.startsWith("morgen")) {
|
|
60
|
+
const tomorrow = new Date(now);
|
|
61
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
62
|
+
const timePart = input.replace("morgen", "").trim();
|
|
63
|
+
if (timePart) {
|
|
64
|
+
const [h, m] = timePart.split(":").map(Number);
|
|
65
|
+
tomorrow.setHours(h || 9, m || 0, 0, 0);
|
|
66
|
+
} else {
|
|
67
|
+
tomorrow.setHours(9, 0, 0, 0);
|
|
68
|
+
}
|
|
69
|
+
return tomorrow;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (input.startsWith("heute")) {
|
|
73
|
+
const today = new Date(now);
|
|
74
|
+
const timePart = input.replace("heute", "").trim();
|
|
75
|
+
if (timePart) {
|
|
76
|
+
const [h, m] = timePart.split(":").map(Number);
|
|
77
|
+
today.setHours(h || 9, m || 0, 0, 0);
|
|
78
|
+
}
|
|
79
|
+
return today;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ISO or other parseable format
|
|
83
|
+
const parsed = new Date(input);
|
|
84
|
+
if (!isNaN(parsed.getTime())) return parsed;
|
|
85
|
+
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default {
|
|
90
|
+
name: "calendar",
|
|
91
|
+
description: "Create, view and manage calendar events",
|
|
92
|
+
version: "1.0.0",
|
|
93
|
+
author: "Alvin Bot",
|
|
94
|
+
|
|
95
|
+
commands: [
|
|
96
|
+
{
|
|
97
|
+
command: "cal",
|
|
98
|
+
description: "View/manage calendar",
|
|
99
|
+
handler: async (ctx, args) => {
|
|
100
|
+
// /cal — show upcoming events
|
|
101
|
+
if (!args) {
|
|
102
|
+
const events = loadEvents()
|
|
103
|
+
.filter(e => new Date(e.date) >= new Date())
|
|
104
|
+
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
|
|
105
|
+
.slice(0, 10);
|
|
106
|
+
|
|
107
|
+
if (events.length === 0) {
|
|
108
|
+
await ctx.reply("📅 No upcoming events.\nNew: `/cal add morgen 14:00 | Meeting`", { parse_mode: "Markdown" });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const lines = events.map((e, i) => {
|
|
113
|
+
const date = formatDate(e.date);
|
|
114
|
+
const loc = e.location ? ` 📍 ${e.location}` : "";
|
|
115
|
+
return `${i + 1}. 📅 *${e.title}*\n ${date}${loc}`;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await ctx.reply(`📅 *Upcoming events:*\n\n${lines.join("\n\n")}`, { parse_mode: "Markdown" });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// /cal add <datetime> | <title> [| location]
|
|
123
|
+
if (args.startsWith("add ")) {
|
|
124
|
+
const text = args.slice(4).trim();
|
|
125
|
+
const parts = text.split("|").map(s => s.trim());
|
|
126
|
+
|
|
127
|
+
if (parts.length < 2) {
|
|
128
|
+
await ctx.reply("Format: `/cal add morgen 14:00 | Event title | Location (optional)`", { parse_mode: "Markdown" });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const dateStr = parts[0];
|
|
133
|
+
const title = parts[1];
|
|
134
|
+
const location = parts[2] || "";
|
|
135
|
+
|
|
136
|
+
const date = parseDateTime(dateStr);
|
|
137
|
+
if (!date) {
|
|
138
|
+
await ctx.reply(`❌ Date not recognized: "${dateStr}"\nTry: \`morgen 14:00\`, \`heute 18:00\`, \`in 2h\`, \`2026-03-01 09:00\``, { parse_mode: "Markdown" });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const event = {
|
|
143
|
+
id: Date.now().toString(36),
|
|
144
|
+
title,
|
|
145
|
+
date: date.toISOString(),
|
|
146
|
+
location,
|
|
147
|
+
created: new Date().toISOString(),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const events = loadEvents();
|
|
151
|
+
events.push(event);
|
|
152
|
+
saveEvents(events);
|
|
153
|
+
|
|
154
|
+
await ctx.reply(
|
|
155
|
+
`✅ *Event created:*\n\n📅 ${title}\n🕐 ${formatDate(date)}${location ? `\n📍 ${location}` : ""}`,
|
|
156
|
+
{ parse_mode: "Markdown" }
|
|
157
|
+
);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// /cal delete <number>
|
|
162
|
+
if (args.startsWith("delete ") || args.startsWith("del ")) {
|
|
163
|
+
const idx = parseInt(args.split(" ")[1]) - 1;
|
|
164
|
+
const events = loadEvents()
|
|
165
|
+
.filter(e => new Date(e.date) >= new Date())
|
|
166
|
+
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
|
|
167
|
+
|
|
168
|
+
if (isNaN(idx) || idx < 0 || idx >= events.length) {
|
|
169
|
+
await ctx.reply("❌ Invalid number. Use `/cal` for the list.", { parse_mode: "Markdown" });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const toDelete = events[idx];
|
|
174
|
+
const allEvents = loadEvents().filter(e => e.id !== toDelete.id);
|
|
175
|
+
saveEvents(allEvents);
|
|
176
|
+
|
|
177
|
+
await ctx.reply(`🗑️ Deleted: *${toDelete.title}*`, { parse_mode: "Markdown" });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// /cal today
|
|
182
|
+
if (args === "today" || args === "heute") {
|
|
183
|
+
const now = new Date();
|
|
184
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
185
|
+
const todayEnd = new Date(todayStart.getTime() + 86400000);
|
|
186
|
+
|
|
187
|
+
const events = loadEvents()
|
|
188
|
+
.filter(e => {
|
|
189
|
+
const d = new Date(e.date);
|
|
190
|
+
return d >= todayStart && d < todayEnd;
|
|
191
|
+
})
|
|
192
|
+
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
|
|
193
|
+
|
|
194
|
+
if (events.length === 0) {
|
|
195
|
+
await ctx.reply("📅 No events today.");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const lines = events.map(e => {
|
|
200
|
+
const time = new Date(e.date).toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" });
|
|
201
|
+
return `🕐 ${time} — *${e.title}*`;
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await ctx.reply(`📅 *Today:*\n\n${lines.join("\n")}`, { parse_mode: "Markdown" });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
await ctx.reply(
|
|
209
|
+
"📅 *Calendar commands:*\n\n" +
|
|
210
|
+
"`/cal` — Upcoming events\n" +
|
|
211
|
+
"`/cal heute` — Today's events\n" +
|
|
212
|
+
"`/cal add morgen 14:00 | Title | Location` — Create event\n" +
|
|
213
|
+
"`/cal delete 1` — Delete event",
|
|
214
|
+
{ parse_mode: "Markdown" }
|
|
215
|
+
);
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
|
|
220
|
+
tools: [
|
|
221
|
+
{
|
|
222
|
+
name: "list_events",
|
|
223
|
+
description: "List upcoming calendar events",
|
|
224
|
+
parameters: {
|
|
225
|
+
type: "object",
|
|
226
|
+
properties: {
|
|
227
|
+
days: { type: "number", description: "Number of days to look ahead (default: 7)" },
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
execute: async (params) => {
|
|
231
|
+
const days = params.days || 7;
|
|
232
|
+
const cutoff = new Date(Date.now() + days * 86400000);
|
|
233
|
+
const events = loadEvents()
|
|
234
|
+
.filter(e => new Date(e.date) >= new Date() && new Date(e.date) <= cutoff)
|
|
235
|
+
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
|
|
236
|
+
return JSON.stringify(events);
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: "create_event",
|
|
241
|
+
description: "Create a calendar event",
|
|
242
|
+
parameters: {
|
|
243
|
+
type: "object",
|
|
244
|
+
properties: {
|
|
245
|
+
title: { type: "string", description: "Event title" },
|
|
246
|
+
date: { type: "string", description: "Date/time (ISO or natural language)" },
|
|
247
|
+
location: { type: "string", description: "Location (optional)" },
|
|
248
|
+
},
|
|
249
|
+
required: ["title", "date"],
|
|
250
|
+
},
|
|
251
|
+
execute: async (params) => {
|
|
252
|
+
const date = parseDateTime(params.date) || new Date(params.date);
|
|
253
|
+
if (isNaN(date.getTime())) return `Invalid date: ${params.date}`;
|
|
254
|
+
|
|
255
|
+
const event = {
|
|
256
|
+
id: Date.now().toString(36),
|
|
257
|
+
title: params.title,
|
|
258
|
+
date: date.toISOString(),
|
|
259
|
+
location: params.location || "",
|
|
260
|
+
created: new Date().toISOString(),
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const events = loadEvents();
|
|
264
|
+
events.push(event);
|
|
265
|
+
saveEvents(events);
|
|
266
|
+
return `Event created: ${params.title} at ${date.toISOString()}`;
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
};
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Plugin — Read and send emails via IMAP/SMTP.
|
|
3
|
+
*
|
|
4
|
+
* Lightweight implementation using raw IMAP/SMTP commands.
|
|
5
|
+
* For full email support, configure in .env:
|
|
6
|
+
* EMAIL_IMAP_HOST=imap.mail.me.com
|
|
7
|
+
* EMAIL_IMAP_PORT=993
|
|
8
|
+
* EMAIL_SMTP_HOST=smtp.mail.me.com
|
|
9
|
+
* EMAIL_SMTP_PORT=587
|
|
10
|
+
* EMAIL_USER=your-email@provider.com
|
|
11
|
+
* EMAIL_PASS=app-specific-password
|
|
12
|
+
*
|
|
13
|
+
* Alternative: Uses `himalaya` CLI if installed (more reliable).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { execSync } from "child_process";
|
|
17
|
+
|
|
18
|
+
function hasHimalaya() {
|
|
19
|
+
try {
|
|
20
|
+
execSync("which himalaya", { stdio: "pipe" });
|
|
21
|
+
return true;
|
|
22
|
+
} catch { return false; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function runHimalaya(args, timeout = 15000) {
|
|
26
|
+
try {
|
|
27
|
+
const result = execSync(`himalaya ${args}`, {
|
|
28
|
+
stdio: "pipe",
|
|
29
|
+
timeout,
|
|
30
|
+
env: { ...process.env, NO_COLOR: "1" },
|
|
31
|
+
});
|
|
32
|
+
return result.toString().trim();
|
|
33
|
+
} catch (err) {
|
|
34
|
+
throw new Error(err.stderr?.toString()?.trim() || err.message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseEmailList(output) {
|
|
39
|
+
// himalaya list outputs a table — parse it
|
|
40
|
+
const lines = output.split("\n").filter(l => l.trim());
|
|
41
|
+
if (lines.length < 2) return [];
|
|
42
|
+
|
|
43
|
+
// Skip header line
|
|
44
|
+
return lines.slice(1).map(line => {
|
|
45
|
+
// Format: ID | FLAGS | FROM | SUBJECT | DATE
|
|
46
|
+
const parts = line.split("|").map(s => s.trim());
|
|
47
|
+
if (parts.length >= 4) {
|
|
48
|
+
return {
|
|
49
|
+
id: parts[0],
|
|
50
|
+
flags: parts[1],
|
|
51
|
+
from: parts[2],
|
|
52
|
+
subject: parts[3],
|
|
53
|
+
date: parts[4] || "",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}).filter(Boolean);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default {
|
|
61
|
+
name: "email",
|
|
62
|
+
description: "E-Mails lesen und senden (via himalaya CLI oder IMAP/SMTP)",
|
|
63
|
+
version: "1.0.0",
|
|
64
|
+
author: "Alvin Bot",
|
|
65
|
+
|
|
66
|
+
onInit: () => {
|
|
67
|
+
if (!hasHimalaya()) {
|
|
68
|
+
console.warn("Email plugin: himalaya CLI not found. Install with: brew install himalaya");
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
commands: [
|
|
73
|
+
{
|
|
74
|
+
command: "email",
|
|
75
|
+
description: "E-Mails verwalten",
|
|
76
|
+
handler: async (ctx, args) => {
|
|
77
|
+
if (!hasHimalaya()) {
|
|
78
|
+
await ctx.reply(
|
|
79
|
+
"📧 *Email Plugin*\n\n" +
|
|
80
|
+
"`himalaya` CLI nicht installiert.\n" +
|
|
81
|
+
"Installiere mit: `brew install himalaya`\n" +
|
|
82
|
+
"Konfiguriere mit: `himalaya account configure`",
|
|
83
|
+
{ parse_mode: "Markdown" }
|
|
84
|
+
);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// /email — list inbox
|
|
89
|
+
if (!args || args === "inbox") {
|
|
90
|
+
try {
|
|
91
|
+
await ctx.api.sendChatAction(ctx.chat.id, "typing");
|
|
92
|
+
const output = runHimalaya("list -s 10");
|
|
93
|
+
const emails = parseEmailList(output);
|
|
94
|
+
|
|
95
|
+
if (emails.length === 0) {
|
|
96
|
+
await ctx.reply("📭 Keine E-Mails im Posteingang.");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const lines = emails.map((e, i) => {
|
|
101
|
+
const unread = e.flags?.includes("Seen") ? "" : "🆕 ";
|
|
102
|
+
return `${unread}*${i + 1}.* ${e.from}\n ${e.subject}`;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await ctx.reply(`📧 *Posteingang (${emails.length}):*\n\n${lines.join("\n\n")}`, { parse_mode: "Markdown" });
|
|
106
|
+
} catch (err) {
|
|
107
|
+
await ctx.reply(`❌ Fehler: ${err.message}`);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// /email read <id>
|
|
113
|
+
if (args.startsWith("read ")) {
|
|
114
|
+
const id = args.slice(5).trim();
|
|
115
|
+
try {
|
|
116
|
+
await ctx.api.sendChatAction(ctx.chat.id, "typing");
|
|
117
|
+
const output = runHimalaya(`read ${id}`);
|
|
118
|
+
const truncated = output.length > 3500 ? output.slice(0, 3500) + "\n\n_[...truncated]_" : output;
|
|
119
|
+
await ctx.reply(`📧 *E-Mail #${id}:*\n\n${truncated}`, { parse_mode: "Markdown" });
|
|
120
|
+
} catch (err) {
|
|
121
|
+
await ctx.reply(`❌ ${err.message}`);
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// /email send <to> | <subject> | <body>
|
|
127
|
+
if (args.startsWith("send ")) {
|
|
128
|
+
const text = args.slice(5).trim();
|
|
129
|
+
const parts = text.split("|").map(s => s.trim());
|
|
130
|
+
|
|
131
|
+
if (parts.length < 3) {
|
|
132
|
+
await ctx.reply("Format: `/email send to@example.com | Betreff | Text`", { parse_mode: "Markdown" });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const [to, subject, ...bodyParts] = parts;
|
|
137
|
+
const body = bodyParts.join("|");
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
await ctx.api.sendChatAction(ctx.chat.id, "typing");
|
|
141
|
+
// Use himalaya write + send
|
|
142
|
+
const mml = `From: \nTo: ${to}\nSubject: ${subject}\n\n${body}`;
|
|
143
|
+
execSync(`echo '${mml.replace(/'/g, "'\\''")}' | himalaya send`, {
|
|
144
|
+
stdio: "pipe",
|
|
145
|
+
timeout: 30000,
|
|
146
|
+
});
|
|
147
|
+
await ctx.reply(`✅ E-Mail gesendet an ${to}`, { parse_mode: "Markdown" });
|
|
148
|
+
} catch (err) {
|
|
149
|
+
await ctx.reply(`❌ Senden fehlgeschlagen: ${err.message}`);
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// /email search <query>
|
|
155
|
+
if (args.startsWith("search ")) {
|
|
156
|
+
const query = args.slice(7).trim();
|
|
157
|
+
try {
|
|
158
|
+
await ctx.api.sendChatAction(ctx.chat.id, "typing");
|
|
159
|
+
const output = runHimalaya(`search "${query}"`, 30000);
|
|
160
|
+
const truncated = output.length > 3000 ? output.slice(0, 3000) + "\n..." : output;
|
|
161
|
+
await ctx.reply(`🔍 *Suche: "${query}"*\n\n${truncated}`, { parse_mode: "Markdown" });
|
|
162
|
+
} catch (err) {
|
|
163
|
+
await ctx.reply(`❌ ${err.message}`);
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
await ctx.reply(
|
|
169
|
+
"📧 *Email-Befehle:*\n\n" +
|
|
170
|
+
"`/email` — Posteingang (letzte 10)\n" +
|
|
171
|
+
"`/email read 123` — E-Mail lesen\n" +
|
|
172
|
+
"`/email send to@x.com | Betreff | Text` — Senden\n" +
|
|
173
|
+
"`/email search Suchbegriff` — Suchen",
|
|
174
|
+
{ parse_mode: "Markdown" }
|
|
175
|
+
);
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
|
|
180
|
+
tools: [
|
|
181
|
+
{
|
|
182
|
+
name: "list_emails",
|
|
183
|
+
description: "List recent emails from inbox",
|
|
184
|
+
parameters: {
|
|
185
|
+
type: "object",
|
|
186
|
+
properties: {
|
|
187
|
+
count: { type: "number", description: "Number of emails (default: 10)" },
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
execute: async (params) => {
|
|
191
|
+
if (!hasHimalaya()) return "himalaya CLI not installed";
|
|
192
|
+
const count = params.count || 10;
|
|
193
|
+
return runHimalaya(`list -s ${count}`);
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: "read_email",
|
|
198
|
+
description: "Read a specific email by ID",
|
|
199
|
+
parameters: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
id: { type: "string", description: "Email ID" },
|
|
203
|
+
},
|
|
204
|
+
required: ["id"],
|
|
205
|
+
},
|
|
206
|
+
execute: async (params) => {
|
|
207
|
+
if (!hasHimalaya()) return "himalaya CLI not installed";
|
|
208
|
+
return runHimalaya(`read ${params.id}`);
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: "send_email",
|
|
213
|
+
description: "Send an email",
|
|
214
|
+
parameters: {
|
|
215
|
+
type: "object",
|
|
216
|
+
properties: {
|
|
217
|
+
to: { type: "string", description: "Recipient email" },
|
|
218
|
+
subject: { type: "string", description: "Email subject" },
|
|
219
|
+
body: { type: "string", description: "Email body text" },
|
|
220
|
+
},
|
|
221
|
+
required: ["to", "subject", "body"],
|
|
222
|
+
},
|
|
223
|
+
execute: async (params) => {
|
|
224
|
+
if (!hasHimalaya()) return "himalaya CLI not installed";
|
|
225
|
+
const mml = `From: \nTo: ${params.to}\nSubject: ${params.subject}\n\n${params.body}`;
|
|
226
|
+
execSync(`echo '${mml.replace(/'/g, "'\\''")}' | himalaya send`, { stdio: "pipe", timeout: 30000 });
|
|
227
|
+
return `Email sent to ${params.to}`;
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
};
|