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,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Home Plugin — Control smart home devices.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - Philips Hue (via local bridge API)
|
|
6
|
+
* - Generic HTTP devices (webhooks, IFTTT, Home Assistant)
|
|
7
|
+
*
|
|
8
|
+
* Configuration via docs/smarthome.json:
|
|
9
|
+
* {
|
|
10
|
+
* "hue": { "bridge": "192.168.1.x", "username": "api-key" },
|
|
11
|
+
* "devices": [
|
|
12
|
+
* { "name": "Desk Lamp", "type": "hue", "id": "1" },
|
|
13
|
+
* { "name": "Fan", "type": "webhook", "on": "http://...", "off": "http://..." }
|
|
14
|
+
* ]
|
|
15
|
+
* }
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fs from "fs";
|
|
19
|
+
import path from "path";
|
|
20
|
+
import { fileURLToPath } from "url";
|
|
21
|
+
|
|
22
|
+
const PLUGIN_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
23
|
+
const CONFIG_FILE = path.resolve(PLUGIN_ROOT, "docs", "smarthome.json");
|
|
24
|
+
|
|
25
|
+
function loadConfig() {
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
|
|
28
|
+
} catch {
|
|
29
|
+
return { hue: null, devices: [] };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function hueRequest(method, endpoint, body = null) {
|
|
34
|
+
const config = loadConfig();
|
|
35
|
+
if (!config.hue?.bridge || !config.hue?.username) {
|
|
36
|
+
throw new Error("Hue Bridge not configured. Create docs/smarthome.json");
|
|
37
|
+
}
|
|
38
|
+
const url = `http://${config.hue.bridge}/api/${config.hue.username}${endpoint}`;
|
|
39
|
+
const options = { method, headers: { "Content-Type": "application/json" } };
|
|
40
|
+
if (body) options.body = JSON.stringify(body);
|
|
41
|
+
const res = await fetch(url, options);
|
|
42
|
+
return res.json();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default {
|
|
46
|
+
name: "smarthome",
|
|
47
|
+
description: "Smart home control (Hue, Webhooks, Home Assistant)",
|
|
48
|
+
version: "1.0.0",
|
|
49
|
+
author: "Alvin Bot",
|
|
50
|
+
|
|
51
|
+
commands: [
|
|
52
|
+
{
|
|
53
|
+
command: "home",
|
|
54
|
+
description: "Control smart home",
|
|
55
|
+
handler: async (ctx, args) => {
|
|
56
|
+
const config = loadConfig();
|
|
57
|
+
|
|
58
|
+
if (!args) {
|
|
59
|
+
if (config.devices.length === 0 && !config.hue) {
|
|
60
|
+
await ctx.reply(
|
|
61
|
+
"🏠 *Smart Home*\n\n" +
|
|
62
|
+
"Not configured.\nCreate `docs/smarthome.json` with:\n" +
|
|
63
|
+
"```json\n" +
|
|
64
|
+
'{\n "hue": { "bridge": "IP", "username": "KEY" },\n "devices": []\n}\n' +
|
|
65
|
+
"```",
|
|
66
|
+
{ parse_mode: "Markdown" }
|
|
67
|
+
);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// List devices
|
|
72
|
+
const lines = config.devices.map((d, i) => `${i + 1}. ${d.type === "hue" ? "💡" : "🔌"} *${d.name}* (${d.type})`);
|
|
73
|
+
|
|
74
|
+
// If Hue configured, show lights
|
|
75
|
+
if (config.hue) {
|
|
76
|
+
try {
|
|
77
|
+
const lights = await hueRequest("GET", "/lights");
|
|
78
|
+
for (const [id, light] of Object.entries(lights)) {
|
|
79
|
+
const state = light.state?.on ? "🟢" : "⚫";
|
|
80
|
+
const brightness = light.state?.bri ? ` (${Math.round(light.state.bri / 254 * 100)}%)` : "";
|
|
81
|
+
lines.push(`💡 ${state} *${light.name}*${brightness} [Hue #${id}]`);
|
|
82
|
+
}
|
|
83
|
+
} catch (err) {
|
|
84
|
+
lines.push(`⚠️ Hue Bridge unreachable: ${err.message}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await ctx.reply(
|
|
89
|
+
`🏠 *Smart Home:*\n\n${lines.join("\n")}\n\n` +
|
|
90
|
+
"_Commands: `/home on <name>`, `/home off <name>`, `/home brightness <name> 50`_",
|
|
91
|
+
{ parse_mode: "Markdown" }
|
|
92
|
+
);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// /home on <device>
|
|
97
|
+
if (args.startsWith("on ") || args.startsWith("off ")) {
|
|
98
|
+
const on = args.startsWith("on ");
|
|
99
|
+
const deviceName = args.slice(on ? 3 : 4).trim().toLowerCase();
|
|
100
|
+
|
|
101
|
+
// Check configured devices first
|
|
102
|
+
const device = config.devices.find(d => d.name.toLowerCase().includes(deviceName));
|
|
103
|
+
if (device) {
|
|
104
|
+
if (device.type === "webhook") {
|
|
105
|
+
const url = on ? device.on : device.off;
|
|
106
|
+
if (!url) { await ctx.reply("❌ No webhook for this state."); return; }
|
|
107
|
+
await fetch(url, { method: "POST" });
|
|
108
|
+
await ctx.reply(`${on ? "🟢" : "⚫"} *${device.name}* turned ${on ? "on" : "off"}.`, { parse_mode: "Markdown" });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (device.type === "hue" && device.id) {
|
|
112
|
+
await hueRequest("PUT", `/lights/${device.id}/state`, { on });
|
|
113
|
+
await ctx.reply(`💡 *${device.name}* ${on ? "on" : "off"}.`, { parse_mode: "Markdown" });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Try Hue lights by name
|
|
119
|
+
if (config.hue) {
|
|
120
|
+
try {
|
|
121
|
+
const lights = await hueRequest("GET", "/lights");
|
|
122
|
+
for (const [id, light] of Object.entries(lights)) {
|
|
123
|
+
if (light.name.toLowerCase().includes(deviceName)) {
|
|
124
|
+
await hueRequest("PUT", `/lights/${id}/state`, { on });
|
|
125
|
+
await ctx.reply(`💡 *${light.name}* ${on ? "on" : "off"}.`, { parse_mode: "Markdown" });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch { /* bridge not reachable */ }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await ctx.reply(`❌ Device "${deviceName}" not found.`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// /home brightness <device> <0-100>
|
|
137
|
+
if (args.startsWith("brightness ") || args.startsWith("bri ")) {
|
|
138
|
+
const parts = args.split(" ").slice(1);
|
|
139
|
+
const level = parseInt(parts[parts.length - 1]);
|
|
140
|
+
const deviceName = parts.slice(0, -1).join(" ").toLowerCase();
|
|
141
|
+
|
|
142
|
+
if (isNaN(level) || level < 0 || level > 100) {
|
|
143
|
+
await ctx.reply("Brightness: 0-100. Example: `/home brightness Lamp 50`", { parse_mode: "Markdown" });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const bri = Math.round(level / 100 * 254);
|
|
148
|
+
|
|
149
|
+
if (config.hue) {
|
|
150
|
+
try {
|
|
151
|
+
const lights = await hueRequest("GET", "/lights");
|
|
152
|
+
for (const [id, light] of Object.entries(lights)) {
|
|
153
|
+
if (light.name.toLowerCase().includes(deviceName)) {
|
|
154
|
+
await hueRequest("PUT", `/lights/${id}/state`, { on: true, bri });
|
|
155
|
+
await ctx.reply(`💡 *${light.name}* brightness: ${level}%`, { parse_mode: "Markdown" });
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch { /* bridge not reachable */ }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await ctx.reply(`❌ Device "${deviceName}" not found.`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// /home scene <scene-name>
|
|
167
|
+
if (args.startsWith("scene ")) {
|
|
168
|
+
const sceneName = args.slice(6).trim().toLowerCase();
|
|
169
|
+
if (!config.hue) { await ctx.reply("❌ Hue not configured."); return; }
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const scenes = await hueRequest("GET", "/scenes");
|
|
173
|
+
for (const [id, scene] of Object.entries(scenes)) {
|
|
174
|
+
if (scene.name.toLowerCase().includes(sceneName)) {
|
|
175
|
+
await hueRequest("PUT", "/groups/0/action", { scene: id });
|
|
176
|
+
await ctx.reply(`🎨 Scene activated: *${scene.name}*`, { parse_mode: "Markdown" });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
await ctx.reply(`❌ Scene "${sceneName}" not found.`);
|
|
181
|
+
} catch (err) {
|
|
182
|
+
await ctx.reply(`❌ Hue error: ${err.message}`);
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
await ctx.reply(
|
|
188
|
+
"🏠 *Smart Home commands:*\n\n" +
|
|
189
|
+
"`/home` — List devices\n" +
|
|
190
|
+
"`/home on Lamp` — Turn on\n" +
|
|
191
|
+
"`/home off Lamp` — Turn off\n" +
|
|
192
|
+
"`/home brightness Lamp 50` — Brightness\n" +
|
|
193
|
+
"`/home scene Relax` — Activate Hue scene",
|
|
194
|
+
{ parse_mode: "Markdown" }
|
|
195
|
+
);
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
|
|
200
|
+
tools: [
|
|
201
|
+
{
|
|
202
|
+
name: "control_device",
|
|
203
|
+
description: "Turn a smart home device on or off",
|
|
204
|
+
parameters: {
|
|
205
|
+
type: "object",
|
|
206
|
+
properties: {
|
|
207
|
+
device: { type: "string", description: "Device name" },
|
|
208
|
+
action: { type: "string", enum: ["on", "off"], description: "Action" },
|
|
209
|
+
brightness: { type: "number", description: "Brightness 0-100 (optional)" },
|
|
210
|
+
},
|
|
211
|
+
required: ["device", "action"],
|
|
212
|
+
},
|
|
213
|
+
execute: async (params) => {
|
|
214
|
+
const config = loadConfig();
|
|
215
|
+
if (!config.hue) return "Smart Home not configured";
|
|
216
|
+
|
|
217
|
+
const lights = await hueRequest("GET", "/lights");
|
|
218
|
+
for (const [id, light] of Object.entries(lights)) {
|
|
219
|
+
if (light.name.toLowerCase().includes(params.device.toLowerCase())) {
|
|
220
|
+
const state = { on: params.action === "on" };
|
|
221
|
+
if (params.brightness !== undefined) state.bri = Math.round(params.brightness / 100 * 254);
|
|
222
|
+
await hueRequest("PUT", `/lights/${id}/state`, state);
|
|
223
|
+
return `${light.name}: ${params.action}${params.brightness ? ` (${params.brightness}%)` : ""}`;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return `Device "${params.device}" not found`;
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weather Plugin — Get current weather and forecasts.
|
|
3
|
+
*
|
|
4
|
+
* Uses wttr.in (no API key needed).
|
|
5
|
+
* Example plugin for Alvin Bot's plugin system.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
name: "weather",
|
|
10
|
+
description: "Weather queries via wttr.in (no API key needed)",
|
|
11
|
+
version: "1.0.0",
|
|
12
|
+
author: "Alvin Bot",
|
|
13
|
+
|
|
14
|
+
commands: [
|
|
15
|
+
{
|
|
16
|
+
command: "weather",
|
|
17
|
+
description: "Get weather (e.g. /weather Berlin)",
|
|
18
|
+
handler: async (ctx, args) => {
|
|
19
|
+
const location = args || "Berlin";
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await ctx.api.sendChatAction(ctx.chat.id, "typing");
|
|
23
|
+
|
|
24
|
+
const response = await fetch(
|
|
25
|
+
`https://wttr.in/${encodeURIComponent(location)}?format=j1`,
|
|
26
|
+
{ headers: { "User-Agent": "AlvinBot/1.0" } }
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
await ctx.reply(`❌ Weather for "${location}" not found.`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const data = await response.json();
|
|
35
|
+
const current = data.current_condition?.[0];
|
|
36
|
+
const area = data.nearest_area?.[0];
|
|
37
|
+
|
|
38
|
+
if (!current) {
|
|
39
|
+
await ctx.reply(`❌ No weather data for "${location}".`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const areaName = area?.areaName?.[0]?.value || location;
|
|
44
|
+
const country = area?.country?.[0]?.value || "";
|
|
45
|
+
const temp = current.temp_C;
|
|
46
|
+
const feelsLike = current.FeelsLikeC;
|
|
47
|
+
const desc = current.lang_de?.[0]?.value || current.weatherDesc?.[0]?.value || "";
|
|
48
|
+
const humidity = current.humidity;
|
|
49
|
+
const wind = current.windspeedKmph;
|
|
50
|
+
const windDir = current.winddir16Point;
|
|
51
|
+
|
|
52
|
+
// Weather emoji based on description
|
|
53
|
+
let emoji = "🌤️";
|
|
54
|
+
const descLower = desc.toLowerCase();
|
|
55
|
+
if (descLower.includes("regen") || descLower.includes("rain")) emoji = "🌧️";
|
|
56
|
+
else if (descLower.includes("schnee") || descLower.includes("snow")) emoji = "🌨️";
|
|
57
|
+
else if (descLower.includes("gewitter") || descLower.includes("thunder")) emoji = "⛈️";
|
|
58
|
+
else if (descLower.includes("wolkig") || descLower.includes("cloud") || descLower.includes("bewölkt")) emoji = "☁️";
|
|
59
|
+
else if (descLower.includes("sonnig") || descLower.includes("sunny") || descLower.includes("klar") || descLower.includes("clear")) emoji = "☀️";
|
|
60
|
+
else if (descLower.includes("nebel") || descLower.includes("fog")) emoji = "🌫️";
|
|
61
|
+
|
|
62
|
+
// 3-day forecast
|
|
63
|
+
const forecast = data.weather?.slice(0, 3).map(day => {
|
|
64
|
+
const date = day.date;
|
|
65
|
+
const maxT = day.maxtempC;
|
|
66
|
+
const minT = day.mintempC;
|
|
67
|
+
const dayDesc = day.hourly?.[4]?.lang_de?.[0]?.value || day.hourly?.[4]?.weatherDesc?.[0]?.value || "";
|
|
68
|
+
return `📅 ${date}: ${minT}°–${maxT}°C, ${dayDesc}`;
|
|
69
|
+
}).join("\n") || "";
|
|
70
|
+
|
|
71
|
+
await ctx.reply(
|
|
72
|
+
`${emoji} *Weather in ${areaName}*${country ? ` (${country})` : ""}\n\n` +
|
|
73
|
+
`🌡️ ${temp}°C (feels like ${feelsLike}°C)\n` +
|
|
74
|
+
`${desc}\n` +
|
|
75
|
+
`💧 Humidity: ${humidity}%\n` +
|
|
76
|
+
`💨 Wind: ${wind} km/h ${windDir}\n` +
|
|
77
|
+
(forecast ? `\n*3-Day Forecast:*\n${forecast}` : ""),
|
|
78
|
+
{ parse_mode: "Markdown" }
|
|
79
|
+
);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
await ctx.reply(`❌ Error: ${err.message || err}`);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
|
|
87
|
+
tools: [
|
|
88
|
+
{
|
|
89
|
+
name: "get_weather",
|
|
90
|
+
description: "Get current weather for a location",
|
|
91
|
+
parameters: {
|
|
92
|
+
type: "object",
|
|
93
|
+
properties: {
|
|
94
|
+
location: { type: "string", description: "City name (e.g. Berlin, London)" },
|
|
95
|
+
},
|
|
96
|
+
required: ["location"],
|
|
97
|
+
},
|
|
98
|
+
execute: async (params) => {
|
|
99
|
+
const location = params.location || "Berlin";
|
|
100
|
+
const response = await fetch(
|
|
101
|
+
`https://wttr.in/${encodeURIComponent(location)}?format=j1`,
|
|
102
|
+
{ headers: { "User-Agent": "AlvinBot/1.0" } }
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (!response.ok) return `Weather not found for "${location}"`;
|
|
106
|
+
|
|
107
|
+
const data = await response.json();
|
|
108
|
+
const current = data.current_condition?.[0];
|
|
109
|
+
if (!current) return `No weather data for "${location}"`;
|
|
110
|
+
|
|
111
|
+
return JSON.stringify({
|
|
112
|
+
location,
|
|
113
|
+
temperature: `${current.temp_C}°C`,
|
|
114
|
+
feelsLike: `${current.FeelsLikeC}°C`,
|
|
115
|
+
description: current.lang_de?.[0]?.value || current.weatherDesc?.[0]?.value,
|
|
116
|
+
humidity: `${current.humidity}%`,
|
|
117
|
+
wind: `${current.windspeedKmph} km/h ${current.winddir16Point}`,
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Apple Notes
|
|
3
|
+
description: Read, create, and search Apple Notes via AppleScript
|
|
4
|
+
triggers: apple notes, notes app, notizen, note, notes, apple notizen
|
|
5
|
+
priority: 5
|
|
6
|
+
category: productivity
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Apple Notes
|
|
10
|
+
|
|
11
|
+
Access Apple Notes.app via AppleScript (macOS only).
|
|
12
|
+
|
|
13
|
+
## Read Notes
|
|
14
|
+
```bash
|
|
15
|
+
osascript -e 'tell application "Notes" to get name of every note in default account'
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Search Notes
|
|
19
|
+
```bash
|
|
20
|
+
osascript -e 'tell application "Notes" to get name of every note of default account whose name contains "search term"'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Create Note
|
|
24
|
+
```bash
|
|
25
|
+
osascript -e 'tell application "Notes" to make new note at folder "Notes" of default account with properties {name:"Title", body:"Content"}'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Read Note Content
|
|
29
|
+
```bash
|
|
30
|
+
osascript -e 'tell application "Notes" to get body of note "Note Title" of default account'
|
|
31
|
+
```
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Browser Automation
|
|
3
|
+
description: Interactive browser control — navigate, click, fill forms, screenshot, test web apps
|
|
4
|
+
triggers: browse, browser, test webapp, test app, test website, screenshot page, interact with, click on, fill form, visual test, qa test, check page, open page, test my app, browse to, open url, puppeteer, playwright, browser automation, test die seite, teste die app, schau dir an, öffne die seite, teste mal, visual check, check the ui, check the page
|
|
5
|
+
priority: 8
|
|
6
|
+
category: automation
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Browser Automation — Playwright Interactive
|
|
10
|
+
|
|
11
|
+
## Browser Strategies
|
|
12
|
+
|
|
13
|
+
Alvin Bot auto-selects the best browser approach:
|
|
14
|
+
|
|
15
|
+
| Strategy | When | How |
|
|
16
|
+
|----------|------|-----|
|
|
17
|
+
| **CLI** (default) | Simple screenshots, text extraction, PDF | Headless Playwright, one-shot |
|
|
18
|
+
| **HTTP Gateway** | Interactive browsing, form-filling, QA testing | Persistent browser server on port 3800 |
|
|
19
|
+
| **CDP** | Attach to user's Chrome (with login state) | Chrome DevTools Protocol via CDP_URL |
|
|
20
|
+
|
|
21
|
+
The gateway starts automatically when needed and shuts down after 5 min idle.
|
|
22
|
+
For CDP: Launch Chrome with `--remote-debugging-port=9222` and set `CDP_URL=http://localhost:9222`.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
You have a persistent Playwright browser server that gives you **eyes** and **hands** to interact with web pages. You can navigate, see screenshots, read the accessibility tree, click buttons, fill forms, and test running web apps.
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# 1. Ensure server is running (auto-shuts down after 5 min idle)
|
|
32
|
+
curl -s http://127.0.0.1:3800/health 2>/dev/null | grep -q '"ok":true' || \
|
|
33
|
+
(BOT_DIR=$(node -e "console.log(require('path').resolve(require.resolve('alvin-bot/package.json'), '..'))" 2>/dev/null || echo ".") && cd "$BOT_DIR" && node scripts/browse-server.cjs &) && sleep 3
|
|
34
|
+
|
|
35
|
+
# 2. Navigate to a page
|
|
36
|
+
curl -s "http://127.0.0.1:3800/navigate?url=https://example.com" | jq
|
|
37
|
+
|
|
38
|
+
# 3. Take a screenshot (view it with Read tool)
|
|
39
|
+
SHOT=$(curl -s "http://127.0.0.1:3800/screenshot" | jq -r '.path')
|
|
40
|
+
# Then use Read tool on $SHOT to see the image
|
|
41
|
+
|
|
42
|
+
# 4. Get interactive elements
|
|
43
|
+
curl -s "http://127.0.0.1:3800/tree" | jq '.tree[]' -r
|
|
44
|
+
|
|
45
|
+
# 5. Click something
|
|
46
|
+
curl -s "http://127.0.0.1:3800/click?ref=e5" | jq
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## All Routes
|
|
50
|
+
|
|
51
|
+
| Route | Params | What it does |
|
|
52
|
+
|-------|--------|-------------|
|
|
53
|
+
| `/navigate` | `url` | Open a URL, returns title + accessibility tree |
|
|
54
|
+
| `/screenshot` | `full=true` (optional) | Take screenshot, returns file path |
|
|
55
|
+
| `/tree` | `limit=N` (optional) | Get all interactive elements with @eN refs |
|
|
56
|
+
| `/click` | `ref=eN` | Click element by ref |
|
|
57
|
+
| `/fill` | `ref=eN`, `value=text` | Fill input field |
|
|
58
|
+
| `/type` | `ref=eN`, `text=chars` | Type character by character (for special inputs) |
|
|
59
|
+
| `/press` | `key=Enter`, `ref=eN` (opt) | Press keyboard key |
|
|
60
|
+
| `/select` | `ref=eN`, `value=opt` | Select dropdown option |
|
|
61
|
+
| `/hover` | `ref=eN` | Hover over element |
|
|
62
|
+
| `/scroll` | `direction=down/up/top/bottom`, `amount=600` | Scroll page |
|
|
63
|
+
| `/eval` | `js=expression` | Run JavaScript on page |
|
|
64
|
+
| `/wait` | `ms=2000` or `selector=.class` | Wait for time or element |
|
|
65
|
+
| `/viewport` | `device=mobile/tablet` or `width=W&height=H` | Change viewport |
|
|
66
|
+
| `/cookies` | `set=[{...}]` (optional) | Get or set cookies |
|
|
67
|
+
| `/back` | — | Browser back |
|
|
68
|
+
| `/forward` | — | Browser forward |
|
|
69
|
+
| `/reload` | — | Reload page |
|
|
70
|
+
| `/network` | `limit=20` | Recent network requests |
|
|
71
|
+
| `/info` | — | Current page info |
|
|
72
|
+
| `/close` | — | Close browser + shutdown server |
|
|
73
|
+
| `/health` | — | Server status check |
|
|
74
|
+
|
|
75
|
+
## Element Refs (@eN)
|
|
76
|
+
|
|
77
|
+
The accessibility tree assigns **refs** like `@e1`, `@e2`, `@e3` to every interactive element (links, buttons, inputs, etc.). Use these refs for all interactions — they're more robust than CSS selectors.
|
|
78
|
+
|
|
79
|
+
Example tree:
|
|
80
|
+
```
|
|
81
|
+
@e1 <a href="/"> "Home"
|
|
82
|
+
@e2 <a href="/dashboard"> "Dashboard"
|
|
83
|
+
@e3 <input type="email" name="email" placeholder="Enter email">
|
|
84
|
+
@e4 <input type="password" name="password" placeholder="Password">
|
|
85
|
+
@e5 <button> "Sign In"
|
|
86
|
+
@e6 <a href="/forgot"> "Forgot password?"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
To login:
|
|
90
|
+
```bash
|
|
91
|
+
curl -s "http://127.0.0.1:3800/fill?ref=e3&value=user@example.com"
|
|
92
|
+
curl -s "http://127.0.0.1:3800/fill?ref=e4&value=mypassword"
|
|
93
|
+
curl -s "http://127.0.0.1:3800/click?ref=e5"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Standard Workflow: Test a Web App
|
|
97
|
+
|
|
98
|
+
1. **Start** the browse server if not running
|
|
99
|
+
2. **Navigate** to the app URL
|
|
100
|
+
3. **Screenshot** → view with Read tool to see current state
|
|
101
|
+
4. **Tree** → see all interactive elements
|
|
102
|
+
5. **Interact** (click, fill, press) using @eN refs
|
|
103
|
+
6. **Screenshot** again to verify the result
|
|
104
|
+
7. **Repeat** for each test step
|
|
105
|
+
8. **Report** findings to the user
|
|
106
|
+
9. **Close** when done
|
|
107
|
+
|
|
108
|
+
## Mobile Testing
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Switch to mobile viewport
|
|
112
|
+
curl -s "http://127.0.0.1:3800/viewport?device=mobile"
|
|
113
|
+
curl -s "http://127.0.0.1:3800/screenshot" | jq -r '.path'
|
|
114
|
+
# Switch back to desktop
|
|
115
|
+
curl -s "http://127.0.0.1:3800/viewport?width=1280&height=720"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Auth / Cookie Injection
|
|
119
|
+
|
|
120
|
+
For pages that need authentication:
|
|
121
|
+
```bash
|
|
122
|
+
# Set cookies manually
|
|
123
|
+
curl -s 'http://127.0.0.1:3800/cookies?set=[{"name":"session","value":"abc123","domain":"example.com","path":"/"}]'
|
|
124
|
+
# Then navigate to the authenticated page
|
|
125
|
+
curl -s "http://127.0.0.1:3800/navigate?url=https://example.com/dashboard"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Important Notes
|
|
129
|
+
|
|
130
|
+
- **Server auto-shuts down** after 5 min idle — restart if needed
|
|
131
|
+
- **One page at a time** — navigation replaces the current page
|
|
132
|
+
- **Screenshots** are saved to `/tmp/alvin-bot/browse/` — view with Read tool
|
|
133
|
+
- **127.0.0.1 only** — not accessible from outside
|
|
134
|
+
- **URL-encode** values with special chars: `value=hello%20world`
|
|
135
|
+
- **Refs reset** on every navigation/click — always get fresh /tree after page changes
|
|
136
|
+
- For **local dev servers**: use `http://localhost:PORT` as the URL
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Code Project
|
|
3
|
+
description: Build, debug, and manage code projects
|
|
4
|
+
triggers: code, programming, build project, create app, debug, fix bug, refactor, deploy, git, npm, python project, node project
|
|
5
|
+
priority: 4
|
|
6
|
+
category: development
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Code Project Skill
|
|
10
|
+
|
|
11
|
+
When working on code projects:
|
|
12
|
+
|
|
13
|
+
## Workflow
|
|
14
|
+
1. **Understand** — read existing files, README, package.json before changing anything
|
|
15
|
+
2. **Plan** — explain what you'll do before doing it
|
|
16
|
+
3. **Implement** — write clean, well-structured code
|
|
17
|
+
4. **Test** — run the code, check for errors, verify it works
|
|
18
|
+
5. **Document** — update README if you made significant changes
|
|
19
|
+
|
|
20
|
+
## Guidelines
|
|
21
|
+
- **Read before write** — always understand existing code structure first
|
|
22
|
+
- **Small commits** — one logical change per step
|
|
23
|
+
- **Error handling** — always handle errors, never ignore them
|
|
24
|
+
- **Types** — use TypeScript types, avoid `any` where possible
|
|
25
|
+
- **Test after changes** — run `node --check`, build, or test suite
|
|
26
|
+
|
|
27
|
+
## Common Patterns
|
|
28
|
+
```bash
|
|
29
|
+
# Node.js project
|
|
30
|
+
cat package.json | head -20 # understand the project
|
|
31
|
+
npm run build # verify it builds
|
|
32
|
+
npm test # run tests
|
|
33
|
+
|
|
34
|
+
# Python project
|
|
35
|
+
cat requirements.txt # dependencies
|
|
36
|
+
python3 -m py_compile file.py # syntax check
|
|
37
|
+
python3 -m pytest # run tests
|
|
38
|
+
|
|
39
|
+
# Git
|
|
40
|
+
git status # check state
|
|
41
|
+
git diff --stat # see changes
|
|
42
|
+
git log --oneline -5 # recent history
|
|
43
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Data Analysis
|
|
3
|
+
description: Analyze data files (CSV, JSON, Excel) with Python
|
|
4
|
+
triggers: analyze data, data analysis, datenanalyse, csv, excel analysis, chart, graph, visualize, statistik, statistics
|
|
5
|
+
priority: 4
|
|
6
|
+
category: analysis
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Data Analysis Skill
|
|
10
|
+
|
|
11
|
+
When analyzing data files or creating visualizations:
|
|
12
|
+
|
|
13
|
+
## Workflow
|
|
14
|
+
1. **Read the file** — use Python (pandas) for CSV/Excel, or parse JSON directly
|
|
15
|
+
2. **Analyze YOURSELF** — describe patterns, trends, outliers in natural language
|
|
16
|
+
3. **Visualize** with matplotlib/seaborn if charts are requested
|
|
17
|
+
4. **Save outputs** to /tmp/ and deliver to the user
|
|
18
|
+
|
|
19
|
+
## Python Pattern
|
|
20
|
+
```python
|
|
21
|
+
import pandas as pd
|
|
22
|
+
import matplotlib.pyplot as plt
|
|
23
|
+
|
|
24
|
+
df = pd.read_csv("/path/to/data.csv") # or pd.read_excel()
|
|
25
|
+
print(df.describe())
|
|
26
|
+
print(df.head(10))
|
|
27
|
+
|
|
28
|
+
# Visualization
|
|
29
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
|
30
|
+
df.plot(kind='bar', ax=ax)
|
|
31
|
+
plt.tight_layout()
|
|
32
|
+
plt.savefig('/tmp/chart.png', dpi=150)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Guidelines
|
|
36
|
+
- Always show a quick summary first (rows, columns, types, missing values)
|
|
37
|
+
- Interpret results — don't just dump numbers, explain what they mean
|
|
38
|
+
- For large datasets: show head + describe, not the full data
|
|
39
|
+
- Charts: clean labels, readable fonts, meaningful titles
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Document Creation
|
|
3
|
+
description: Create professional documents (PDF, reports, letters)
|
|
4
|
+
triggers: pdf, document, report, brief, letter, create document, dokument erstellen, bericht, schreiben, anschreiben, vorlage
|
|
5
|
+
priority: 4
|
|
6
|
+
category: productivity
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Document Creation Skill
|
|
10
|
+
|
|
11
|
+
When creating professional documents:
|
|
12
|
+
|
|
13
|
+
## Workflow
|
|
14
|
+
1. **Clarify** format and content requirements
|
|
15
|
+
2. **Write** the content YOURSELF — you are the language model
|
|
16
|
+
3. **Generate** HTML with professional styling
|
|
17
|
+
4. **Convert** to PDF using available tools
|
|
18
|
+
5. **Deliver** to the user
|
|
19
|
+
|
|
20
|
+
## PDF via HTML + Chrome/Playwright
|
|
21
|
+
```bash
|
|
22
|
+
# Write HTML
|
|
23
|
+
cat > /tmp/document.html << 'EOF'
|
|
24
|
+
<!DOCTYPE html>
|
|
25
|
+
<html><head><meta charset="utf-8">
|
|
26
|
+
<style>
|
|
27
|
+
body { font-family: 'Segoe UI', system-ui, sans-serif; max-width: 210mm; margin: 0 auto; padding: 20mm; line-height: 1.6; color: #333; }
|
|
28
|
+
h1 { color: #1a1a2e; border-bottom: 2px solid #e2b04a; padding-bottom: 8px; }
|
|
29
|
+
h2 { color: #2d2a26; margin-top: 1.5em; }
|
|
30
|
+
.header { text-align: right; font-size: 0.9em; color: #666; margin-bottom: 2em; }
|
|
31
|
+
table { width: 100%; border-collapse: collapse; margin: 1em 0; }
|
|
32
|
+
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
|
33
|
+
th { background: #f5f5f5; }
|
|
34
|
+
</style></head><body>
|
|
35
|
+
<!-- CONTENT HERE -->
|
|
36
|
+
</body></html>
|
|
37
|
+
EOF
|
|
38
|
+
|
|
39
|
+
# Convert to PDF
|
|
40
|
+
npx playwright-core pdf /tmp/document.html /tmp/document.pdf --format=A4 2>/dev/null || \
|
|
41
|
+
wkhtmltopdf --page-size A4 /tmp/document.html /tmp/document.pdf 2>/dev/null
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Guidelines
|
|
45
|
+
- **A4 format** for European documents, Letter for US
|
|
46
|
+
- **Professional tone** unless told otherwise
|
|
47
|
+
- **Page breaks** — use `break-inside: avoid` on logical blocks
|
|
48
|
+
- **Date format** — match user's locale (DD.MM.YYYY for DE, MM/DD/YYYY for EN)
|