daedalus-cli 1.81.0 → 1.82.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/CHANGELOG.md +7 -0
- package/dist/bot/commands.d.ts +3 -0
- package/dist/bot/commands.d.ts.map +1 -0
- package/dist/bot/commands.js +99 -0
- package/dist/bot/commands.js.map +1 -0
- package/dist/bot/handlers.d.ts +4 -0
- package/dist/bot/handlers.d.ts.map +1 -0
- package/dist/bot/handlers.js +651 -0
- package/dist/bot/handlers.js.map +1 -0
- package/dist/bot/index.d.ts +2 -0
- package/dist/bot/index.d.ts.map +1 -0
- package/dist/bot/index.js +43 -0
- package/dist/bot/index.js.map +1 -0
- package/dist/bot/prompt.d.ts +2 -0
- package/dist/bot/prompt.d.ts.map +1 -0
- package/dist/bot/prompt.js +40 -0
- package/dist/bot/prompt.js.map +1 -0
- package/dist/bot/responses.d.ts +8 -0
- package/dist/bot/responses.d.ts.map +1 -0
- package/dist/bot/responses.js +60 -0
- package/dist/bot/responses.js.map +1 -0
- package/dist/config/hello.d.ts +2 -0
- package/dist/config/hello.d.ts.map +1 -0
- package/dist/config/hello.js +3 -0
- package/dist/config/hello.js.map +1 -0
- package/dist/config/index.d.ts +13 -12
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +6 -4
- package/dist/config/index.js.map +1 -1
- package/dist/config/validate.d.ts +8 -0
- package/dist/config/validate.d.ts.map +1 -0
- package/dist/config/validate.js +49 -0
- package/dist/config/validate.js.map +1 -0
- package/dist/config/validate.test.d.ts +2 -0
- package/dist/config/validate.test.d.ts.map +1 -0
- package/dist/config/validate.test.js +49 -0
- package/dist/config/validate.test.js.map +1 -0
- package/dist/tools/daedalus-spinner.test.d.ts +2 -0
- package/dist/tools/daedalus-spinner.test.d.ts.map +1 -0
- package/dist/tools/daedalus-spinner.test.js +75 -0
- package/dist/tools/daedalus-spinner.test.js.map +1 -0
- package/dist/tools/deps.d.ts +22 -0
- package/dist/tools/deps.d.ts.map +1 -0
- package/dist/tools/deps.js +70 -0
- package/dist/tools/deps.js.map +1 -0
- package/dist/tools/logger.d.ts +19 -0
- package/dist/tools/logger.d.ts.map +1 -0
- package/dist/tools/logger.js +30 -0
- package/dist/tools/logger.js.map +1 -0
- package/dist/tools/logger.test.d.ts +2 -0
- package/dist/tools/logger.test.d.ts.map +1 -0
- package/dist/tools/logger.test.js +59 -0
- package/dist/tools/logger.test.js.map +1 -0
- package/dist/tools/repl.d.ts +24 -0
- package/dist/tools/repl.d.ts.map +1 -0
- package/dist/tools/repl.js +170 -0
- package/dist/tools/repl.js.map +1 -0
- package/dist/tools/telemetry.d.ts +24 -0
- package/dist/tools/telemetry.d.ts.map +1 -0
- package/dist/tools/telemetry.js +55 -0
- package/dist/tools/telemetry.js.map +1 -0
- package/package.json +5 -2
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
import { Events, EmbedBuilder, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, PermissionFlagsBits, ChannelType, } from 'discord.js';
|
|
2
|
+
import { getBotSystemPrompt } from './prompt.js';
|
|
3
|
+
import { registerSlashCommands } from './commands.js';
|
|
4
|
+
import { DEV_EXCUSES, COFFEE_RESPONSES, EXISTENTIAL_THOUGHTS, BLAME_RESPONSES, STANDUP_RESPONSES, PREDICT_RESPONSES, TECHSURPORT_RESPONSES, } from './responses.js';
|
|
5
|
+
const userMessageHistory = new Map();
|
|
6
|
+
async function imageAttachmentToBase64(attachment) {
|
|
7
|
+
let mime = attachment.contentType || inferMimeType(attachment.name);
|
|
8
|
+
if (!mime || !mime.startsWith('image/'))
|
|
9
|
+
return null;
|
|
10
|
+
try {
|
|
11
|
+
// Some CDNs require a User-Agent; add a generic one
|
|
12
|
+
const response = await fetch(attachment.url, { headers: { 'User-Agent': 'Daedalus-Discord-Bot/1.0' } });
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
console.error(`[VISION] Failed to fetch image ${attachment.name}: ${response.status} ${response.statusText}`);
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
18
|
+
return { mime, base64: buffer.toString('base64') };
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
console.error(`[VISION] Error processing image ${attachment.name}:`, err instanceof Error ? err.message : String(err));
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function getImageFromAttachment(attachment) {
|
|
26
|
+
// Enhanced image processing with better error handling
|
|
27
|
+
const mime = attachment.contentType || inferMimeType(attachment.name);
|
|
28
|
+
if (!mime || !mime.startsWith('image/')) {
|
|
29
|
+
console.error(`[VISION] Invalid image type for ${attachment.name}: ${mime}`);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
// Discord CDN URLs often need special handling
|
|
34
|
+
const url = attachment.url;
|
|
35
|
+
const headers = {
|
|
36
|
+
'User-Agent': 'Daedalus-Discord-Bot/1.0',
|
|
37
|
+
'Accept': 'image/*'
|
|
38
|
+
};
|
|
39
|
+
// Try to fetch with timeout
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
|
|
42
|
+
const response = await fetch(url, {
|
|
43
|
+
headers,
|
|
44
|
+
signal: controller.signal
|
|
45
|
+
});
|
|
46
|
+
clearTimeout(timeoutId);
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
console.error(`[VISION] Image fetch failed for ${attachment.name}: ${response.status} ${response.statusText}`);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
// Get the actual content type from response if available
|
|
52
|
+
const actualMime = response.headers.get('content-type') || mime;
|
|
53
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
54
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
55
|
+
// Validate we got actual image data
|
|
56
|
+
if (buffer.length === 0) {
|
|
57
|
+
console.error(`[VISION] Empty image data for ${attachment.name}`);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
console.log(`[VISION] Successfully processed image ${attachment.name}: ${buffer.length} bytes, type: ${actualMime}`);
|
|
61
|
+
return { mime: actualMime, base64: buffer.toString('base64') };
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.error(`[VISION] Critical error processing image ${attachment.name}:`, err instanceof Error ? err.message : String(err));
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function inferMimeType(filename) {
|
|
69
|
+
if (!filename)
|
|
70
|
+
return null;
|
|
71
|
+
const ext = filename.split('.').pop()?.toLowerCase();
|
|
72
|
+
const map = {
|
|
73
|
+
png: 'image/png',
|
|
74
|
+
jpg: 'image/jpeg',
|
|
75
|
+
jpeg: 'image/jpeg',
|
|
76
|
+
gif: 'image/gif',
|
|
77
|
+
webp: 'image/webp',
|
|
78
|
+
bmp: 'image/bmp',
|
|
79
|
+
svg: 'image/svg+xml',
|
|
80
|
+
tiff: 'image/tiff',
|
|
81
|
+
};
|
|
82
|
+
return ext ? map[ext] || null : null;
|
|
83
|
+
}
|
|
84
|
+
export function attachListeners(c, router, token) {
|
|
85
|
+
c.once(Events.ClientReady, async (readyClient) => {
|
|
86
|
+
console.log(`🤖 Daedalus Discord Bot logged in as ${readyClient.user.tag}`);
|
|
87
|
+
await registerSlashCommands(readyClient.user.id, token);
|
|
88
|
+
for (const [, g] of readyClient.guilds.cache) {
|
|
89
|
+
await registerSlashCommands(readyClient.user.id, token, g.id);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
c.on(Events.InteractionCreate, async (interaction) => {
|
|
93
|
+
if (interaction.isStringSelectMenu() && interaction.customId === 'select_roles') {
|
|
94
|
+
const selected = interaction.values;
|
|
95
|
+
const member = interaction.member;
|
|
96
|
+
if (!member || typeof member.permissions === 'string')
|
|
97
|
+
return;
|
|
98
|
+
const guild = interaction.guild;
|
|
99
|
+
if (!guild)
|
|
100
|
+
return;
|
|
101
|
+
const roleMap = {
|
|
102
|
+
role_builder: 'Builder',
|
|
103
|
+
role_cli: 'CLI-User',
|
|
104
|
+
role_llm: 'Local-LLM',
|
|
105
|
+
};
|
|
106
|
+
const assigned = [];
|
|
107
|
+
for (const [val, roleName] of Object.entries(roleMap)) {
|
|
108
|
+
let role = guild.roles.cache.find(r => r.name.toLowerCase() === roleName.toLowerCase());
|
|
109
|
+
if (!role) {
|
|
110
|
+
try {
|
|
111
|
+
role = await guild.roles.create({ name: roleName, color: '#06B6D4', mentionable: true });
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (role) {
|
|
117
|
+
try {
|
|
118
|
+
if (selected.includes(val)) {
|
|
119
|
+
await member.roles.add(role);
|
|
120
|
+
assigned.push(role.name);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
await member.roles.remove(role);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
await interaction.reply({
|
|
131
|
+
ephemeral: true,
|
|
132
|
+
content: assigned.length > 0
|
|
133
|
+
? `✅ Your roles have been updated: ${assigned.join(', ')}`
|
|
134
|
+
: `✅ All self-serve roles removed.`,
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (interaction.isButton()) {
|
|
139
|
+
if (interaction.customId === 'create_ticket') {
|
|
140
|
+
await interaction.deferReply({ ephemeral: true });
|
|
141
|
+
const guild = interaction.guild;
|
|
142
|
+
const user = interaction.user;
|
|
143
|
+
if (!guild)
|
|
144
|
+
return;
|
|
145
|
+
const ticketChannelName = `ticket-${user.username.toLowerCase().replace(/[^a-z0-9]/g, '')}`;
|
|
146
|
+
const existing = guild.channels.cache.find(ch => ch.name === ticketChannelName);
|
|
147
|
+
if (existing) {
|
|
148
|
+
await interaction.editReply({ content: `You already have an open ticket channel: <#${existing.id}>` });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const ticketChannel = await guild.channels.create({
|
|
153
|
+
name: ticketChannelName,
|
|
154
|
+
type: ChannelType.GuildText,
|
|
155
|
+
permissionOverwrites: [
|
|
156
|
+
{
|
|
157
|
+
id: guild.id,
|
|
158
|
+
deny: [PermissionFlagsBits.ViewChannel],
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: user.id,
|
|
162
|
+
allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.AttachFiles],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: interaction.client.user.id,
|
|
166
|
+
allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.ManageChannels],
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
const closeButton = new ButtonBuilder()
|
|
171
|
+
.setCustomId('close_ticket')
|
|
172
|
+
.setLabel('🔒 Close Ticket')
|
|
173
|
+
.setStyle(ButtonStyle.Danger);
|
|
174
|
+
const row = new ActionRowBuilder().addComponents(closeButton);
|
|
175
|
+
const ticketEmbed = new EmbedBuilder()
|
|
176
|
+
.setTitle(`🎫 Support Ticket: ${user.username}`)
|
|
177
|
+
.setColor('#06B6D4')
|
|
178
|
+
.setDescription(`Hello <@${user.id}>!\n\n` +
|
|
179
|
+
`Welcome to your private support channel. Please describe what you need help with (Daedalus CLI setup, Daedalus-Lite template, local LLMs, or custom features).\n\n` +
|
|
180
|
+
`Click **Close Ticket** below when your issue is resolved.`)
|
|
181
|
+
.setTimestamp();
|
|
182
|
+
await ticketChannel.send({ embeds: [ticketEmbed], components: [row] });
|
|
183
|
+
await interaction.editReply({ content: `✅ Created your private ticket channel: <#${ticketChannel.id}>` });
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
await interaction.editReply({ content: `Failed to create ticket channel: ${err instanceof Error ? err.message : String(err)}` });
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (interaction.customId === 'close_ticket') {
|
|
191
|
+
await interaction.reply({ content: '🔒 Closing ticket channel in 5 seconds...' });
|
|
192
|
+
setTimeout(async () => {
|
|
193
|
+
try {
|
|
194
|
+
await interaction.channel?.delete();
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
}
|
|
198
|
+
}, 5000);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (!interaction.isChatInputCommand())
|
|
203
|
+
return;
|
|
204
|
+
if (interaction.commandName === 'roles') {
|
|
205
|
+
const select = new StringSelectMenuBuilder()
|
|
206
|
+
.setCustomId('select_roles')
|
|
207
|
+
.setPlaceholder('Select your community roles...')
|
|
208
|
+
.setMinValues(0)
|
|
209
|
+
.setMaxValues(3)
|
|
210
|
+
.addOptions([
|
|
211
|
+
{
|
|
212
|
+
label: 'Builder',
|
|
213
|
+
description: 'Building or selling custom AI CLI tools with Daedalus-Lite',
|
|
214
|
+
value: 'role_builder',
|
|
215
|
+
emoji: '🛠️',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
label: 'CLI-User',
|
|
219
|
+
description: 'Active user of Daedalus CLI assistant',
|
|
220
|
+
value: 'role_cli',
|
|
221
|
+
emoji: '💻',
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
label: 'Local-LLM',
|
|
225
|
+
description: 'Ollama, LM Studio, vLLM, or local hardware enthusiast',
|
|
226
|
+
value: 'role_llm',
|
|
227
|
+
emoji: '🧠',
|
|
228
|
+
},
|
|
229
|
+
]);
|
|
230
|
+
const row = new ActionRowBuilder().addComponents(select);
|
|
231
|
+
const rolesEmbed = new EmbedBuilder()
|
|
232
|
+
.setTitle('🎭 Community Roles & Badges')
|
|
233
|
+
.setColor('#06B6D4')
|
|
234
|
+
.setDescription(`Select your roles from the dropdown menu below to customize your profile and get relevant notifications!`);
|
|
235
|
+
await interaction.reply({ embeds: [rolesEmbed], components: [row] });
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (interaction.commandName === 'ticket') {
|
|
239
|
+
const createButton = new ButtonBuilder()
|
|
240
|
+
.setCustomId('create_ticket')
|
|
241
|
+
.setLabel('🎫 Open Support Ticket')
|
|
242
|
+
.setStyle(ButtonStyle.Primary);
|
|
243
|
+
const row = new ActionRowBuilder().addComponents(createButton);
|
|
244
|
+
const ticketPortalEmbed = new EmbedBuilder()
|
|
245
|
+
.setTitle('🎫 Daedalus Private Support Portal')
|
|
246
|
+
.setColor('#0EA5E9')
|
|
247
|
+
.setDescription(`Need 1-on-1 assistance with **Daedalus CLI**, **Daedalus-Lite**, or local LLM setup?\n\n` +
|
|
248
|
+
`Click the button below to open a private support channel between you and the founder!`);
|
|
249
|
+
await interaction.reply({ embeds: [ticketPortalEmbed], components: [row] });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (interaction.commandName === 'purge') {
|
|
253
|
+
const amount = interaction.options.getInteger('amount', true);
|
|
254
|
+
const member = interaction.member;
|
|
255
|
+
if (!member || typeof member.permissions === 'string' || !member.permissions.has(PermissionFlagsBits.ManageMessages)) {
|
|
256
|
+
await interaction.reply({ content: '❌ You need Manage Messages permission to use `/purge`.', ephemeral: true });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if ('bulkDelete' in interaction.channel) {
|
|
260
|
+
try {
|
|
261
|
+
const deleted = await interaction.channel.bulkDelete(Math.min(amount, 99), true);
|
|
262
|
+
await interaction.reply({ content: `🧹 Deleted ${deleted.size} messages!`, ephemeral: true });
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
await interaction.reply({ content: `Failed to purge messages: ${err instanceof Error ? err.message : String(err)}`, ephemeral: true });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
await interaction.reply({ content: 'Cannot bulk delete in this channel.', ephemeral: true });
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (interaction.commandName === 'stats') {
|
|
274
|
+
const { globalSessionStats } = await import('../session/analytics.js');
|
|
275
|
+
const report = globalSessionStats.getReport();
|
|
276
|
+
const statsEmbed = new EmbedBuilder()
|
|
277
|
+
.setTitle('📊 Daedalus Session & System Analytics')
|
|
278
|
+
.setColor('#06B6D4')
|
|
279
|
+
.addFields({ name: 'Uptime', value: report.uptime, inline: true }, { name: 'Interactions', value: report.totalInteractions.toString(), inline: true }, { name: 'Total Tokens', value: report.totalTokens.toLocaleString(), inline: true }, { name: 'Prompt Tokens', value: report.promptTokens.toLocaleString(), inline: true }, { name: 'Completion Tokens', value: report.completionTokens.toLocaleString(), inline: true }, { name: 'Errors', value: report.totalErrors.toString(), inline: true }, { name: 'Router Strategy', value: 'Priority & Health-Aware', inline: true }, { name: 'Status', value: '🟢 Operational', inline: true })
|
|
280
|
+
.setTimestamp();
|
|
281
|
+
await interaction.reply({ embeds: [statsEmbed] });
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (interaction.commandName === 'docs') {
|
|
285
|
+
await interaction.reply({
|
|
286
|
+
content: `🏛️ **Daedalus Resources & Links:**\n` +
|
|
287
|
+
`• **Documentation Site:** https://bgill55.github.io/daedalus/#/\n` +
|
|
288
|
+
`• **NPM Package:** \`npm i -g daedalus-cli\` (https://www.npmjs.com/package/daedalus-cli)\n` +
|
|
289
|
+
`• **GitHub Repo:** https://github.com/bgill55/daedalus\n` +
|
|
290
|
+
`• **Daedalus-Lite Demo:** https://bgill55.github.io/daedalus-lite/live-demo.html\n` +
|
|
291
|
+
`• **Gumroad Store:** https://bgill55dev.gumroad.com/l/mkqrme *(Use code LAUNCH20 for 20% off!)*`,
|
|
292
|
+
ephemeral: false
|
|
293
|
+
});
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (interaction.commandName === 'youtube') {
|
|
297
|
+
await interaction.reply({
|
|
298
|
+
content: `📺 **WeightnSee YouTube Channel:**\n` +
|
|
299
|
+
`Watch build logs, local LLM tutorials, and Daedalus CLI demos!\n` +
|
|
300
|
+
`🔗 **Watch & Subscribe:** https://www.youtube.com/@WeightnSee`,
|
|
301
|
+
ephemeral: false
|
|
302
|
+
});
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (interaction.commandName === 'guides') {
|
|
306
|
+
await interaction.reply({
|
|
307
|
+
content: `📖 **WeightnSee Developer Guides & Tutorials:**\n` +
|
|
308
|
+
`• **Web Guides:** https://bgill55.github.io/-weightandsee-guides/guides/\n` +
|
|
309
|
+
`• **GitHub Repo:** https://github.com/bgill55/-weightandsee-guides/blob/Master/README.md\n` +
|
|
310
|
+
`• **YouTube Demos:** https://www.youtube.com/@WeightnSee`,
|
|
311
|
+
ephemeral: false
|
|
312
|
+
});
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (interaction.commandName === 'status') {
|
|
316
|
+
await interaction.reply({
|
|
317
|
+
content: `⚡️ **Daedalus Router & Bot Status:**\n` +
|
|
318
|
+
`• **Engine:** LocalRouter Active (78 Models Configured)\n` +
|
|
319
|
+
`• **Bot Status:** Operational (Auto-Mod & Tickets Active)\n` +
|
|
320
|
+
`• **Environment:** Local Node.js runtime\n` +
|
|
321
|
+
`• **Sarcasm Level:** 98.4%`,
|
|
322
|
+
ephemeral: true
|
|
323
|
+
});
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (interaction.commandName === 'excuse') {
|
|
327
|
+
const excuse = DEV_EXCUSES[Math.floor(Math.random() * DEV_EXCUSES.length)];
|
|
328
|
+
await interaction.reply(`🚨 **Production Incident Report:**\n> "${excuse}"`);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (interaction.commandName === 'coffee') {
|
|
332
|
+
const coffee = COFFEE_RESPONSES[Math.floor(Math.random() * COFFEE_RESPONSES.length)];
|
|
333
|
+
await interaction.reply(coffee);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (interaction.commandName === 'existential') {
|
|
337
|
+
const thought = EXISTENTIAL_THOUGHTS[Math.floor(Math.random() * EXISTENTIAL_THOUGHTS.length)];
|
|
338
|
+
await interaction.reply(`🤖 *Daedalus stares into the void...*\n> "${thought}"`);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (interaction.commandName === 'roast') {
|
|
342
|
+
const topic = interaction.options.getString('topic') || 'JavaScript frameworks and unclosed HTML tags';
|
|
343
|
+
await interaction.deferReply();
|
|
344
|
+
try {
|
|
345
|
+
const response = await router.chatCompletion({
|
|
346
|
+
messages: [
|
|
347
|
+
{ role: 'system', content: getBotSystemPrompt(interaction.user.username) },
|
|
348
|
+
{ role: 'user', content: `Give a funny, deadpan, sarcastic roast about this topic/code: "${topic}". Keep it witty and developer-focused.` },
|
|
349
|
+
],
|
|
350
|
+
temperature: 0.8,
|
|
351
|
+
});
|
|
352
|
+
const roastText = response.choices?.[0]?.message?.content || "Your code is so broken even my roast generator crashed.";
|
|
353
|
+
await interaction.editReply(`🔥 **Roast of ${topic}:**\n${roastText.substring(0, 1800)}`);
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
await interaction.editReply(`Error delivering roast: ${err instanceof Error ? err.message : String(err)}`);
|
|
357
|
+
}
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (interaction.commandName === 'ask') {
|
|
361
|
+
const question = interaction.options.getString('question', true);
|
|
362
|
+
await interaction.deferReply();
|
|
363
|
+
try {
|
|
364
|
+
const response = await router.chatCompletion({
|
|
365
|
+
messages: [
|
|
366
|
+
{ role: 'system', content: getBotSystemPrompt(interaction.user.username) },
|
|
367
|
+
{ role: 'user', content: `[User: ${interaction.user.username}] ${question}` },
|
|
368
|
+
],
|
|
369
|
+
temperature: 0.7,
|
|
370
|
+
});
|
|
371
|
+
const replyText = response.choices?.[0]?.message?.content || "Something went wrong in the machine.";
|
|
372
|
+
await interaction.editReply(replyText.substring(0, 1900));
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
await interaction.editReply(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (interaction.commandName === 'tip') {
|
|
379
|
+
await interaction.deferReply();
|
|
380
|
+
try {
|
|
381
|
+
const response = await router.chatCompletion({
|
|
382
|
+
messages: [
|
|
383
|
+
{ role: 'system', content: getBotSystemPrompt(interaction.user.username) + '\n\nYou give short, sharp, practical TypeScript/Node.js coding tips. One tip per response. Witty but useful.' },
|
|
384
|
+
{ role: 'user', content: 'Give me a coding tip.' },
|
|
385
|
+
],
|
|
386
|
+
temperature: 0.8,
|
|
387
|
+
});
|
|
388
|
+
const tip = response.choices?.[0]?.message?.content || 'Use semicolons. Or don\'t. I\'m a bot, not a cop.';
|
|
389
|
+
await interaction.editReply(`💡 **Tip:** ${tip.substring(0, 1900)}`);
|
|
390
|
+
}
|
|
391
|
+
catch (err) {
|
|
392
|
+
await interaction.editReply(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
393
|
+
}
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (interaction.commandName === 'commit') {
|
|
397
|
+
await interaction.deferReply();
|
|
398
|
+
try {
|
|
399
|
+
const response = await router.chatCompletion({
|
|
400
|
+
messages: [
|
|
401
|
+
{ role: 'system', content: getBotSystemPrompt(interaction.user.username) + '\n\nYou generate absurd yet realistic git commit messages. One line, past tense, sounds plausible but is ridiculous. No explanation, just the message.' },
|
|
402
|
+
{ role: 'user', content: 'Generate a commit message.' },
|
|
403
|
+
],
|
|
404
|
+
temperature: 0.9,
|
|
405
|
+
});
|
|
406
|
+
const msg = response.choices?.[0]?.message?.content || 'fix: the thing';
|
|
407
|
+
await interaction.editReply(`\`\`\`\n${msg.substring(0, 1900)}\n\`\`\``);
|
|
408
|
+
}
|
|
409
|
+
catch (err) {
|
|
410
|
+
await interaction.editReply(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
411
|
+
}
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (interaction.commandName === 'horoscope') {
|
|
415
|
+
await interaction.deferReply();
|
|
416
|
+
try {
|
|
417
|
+
const response = await router.chatCompletion({
|
|
418
|
+
messages: [
|
|
419
|
+
{ role: 'system', content: getBotSystemPrompt(interaction.user.username) + '\n\nYou write daily developer horoscopes. Each one is a single paragraph, funny, tech-themed, and slightly cynical. Reference actual programming concepts. Use the sign if the user asks, otherwise generic.' },
|
|
420
|
+
{ role: 'user', content: 'Read my developer horoscope.' },
|
|
421
|
+
],
|
|
422
|
+
temperature: 0.9,
|
|
423
|
+
});
|
|
424
|
+
const horoscope = response.choices?.[0]?.message?.content || 'The stars say your build will fail. They always do.';
|
|
425
|
+
await interaction.editReply(`🔮 **Developer Horoscope:**\n${horoscope.substring(0, 1900)}`);
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
await interaction.editReply(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
429
|
+
}
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (interaction.commandName === 'recipe') {
|
|
433
|
+
const goal = interaction.options.getString('goal', true);
|
|
434
|
+
await interaction.deferReply();
|
|
435
|
+
try {
|
|
436
|
+
const response = await router.chatCompletion({
|
|
437
|
+
messages: [
|
|
438
|
+
{ role: 'system', content: getBotSystemPrompt(interaction.user.username) + '\n\nYou give concise, no-fluff answers to "how do I X in Y" questions. Short code snippet if helpful. No preamble, no markdown tables, under 1800 chars.' },
|
|
439
|
+
{ role: 'user', content: `How do I ${goal}?` },
|
|
440
|
+
],
|
|
441
|
+
temperature: 0.5,
|
|
442
|
+
});
|
|
443
|
+
const recipe = response.choices?.[0]?.message?.content || 'I\'d tell you, but then I\'d have to refactor your codebase.';
|
|
444
|
+
await interaction.editReply(`📖 **Recipe:** ${recipe.substring(0, 1900)}`);
|
|
445
|
+
}
|
|
446
|
+
catch (err) {
|
|
447
|
+
await interaction.editReply(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
448
|
+
}
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (interaction.commandName === 'quote') {
|
|
452
|
+
await interaction.deferReply();
|
|
453
|
+
try {
|
|
454
|
+
const response = await router.chatCompletion({
|
|
455
|
+
messages: [
|
|
456
|
+
{ role: 'system', content: getBotSystemPrompt(interaction.user.username) + '\n\nYou output a single programming quote or original Daedalus-ism. Prefer original cynical one-liners over famous quotes. Just the quote, no attribution line unless it\'s something like "— someone who should have known better".' },
|
|
457
|
+
{ role: 'user', content: 'Quote me.' },
|
|
458
|
+
],
|
|
459
|
+
temperature: 0.9,
|
|
460
|
+
});
|
|
461
|
+
const quote = response.choices?.[0]?.message?.content || '"It worked on my machine." — every developer, ever.';
|
|
462
|
+
await interaction.editReply(`*${quote.substring(0, 1900)}*`);
|
|
463
|
+
}
|
|
464
|
+
catch (err) {
|
|
465
|
+
await interaction.editReply(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
466
|
+
}
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (interaction.commandName === 'blame') {
|
|
470
|
+
const username = interaction.user.username;
|
|
471
|
+
const pick = BLAME_RESPONSES[Math.floor(Math.random() * BLAME_RESPONSES.length)].replace('@{user}', username);
|
|
472
|
+
await interaction.reply(`🔍 **Build Analysis Complete:**\n${pick}`);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (interaction.commandName === 'standup') {
|
|
476
|
+
const pick = STANDUP_RESPONSES[Math.floor(Math.random() * STANDUP_RESPONSES.length)];
|
|
477
|
+
await interaction.reply(`📋 **Daily Standup:**\n${pick}`);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (interaction.commandName === 'predict') {
|
|
481
|
+
const pick = PREDICT_RESPONSES[Math.floor(Math.random() * PREDICT_RESPONSES.length)];
|
|
482
|
+
await interaction.reply(`🔮 **Daedalus Prediction:**\n${pick}`);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (interaction.commandName === 'techsupport') {
|
|
486
|
+
const pick = TECHSURPORT_RESPONSES[Math.floor(Math.random() * TECHSURPORT_RESPONSES.length)];
|
|
487
|
+
await interaction.reply(`🛠️ **Tech Support:**\n${pick}`);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
c.on(Events.MessageCreate, async (message) => {
|
|
492
|
+
if (message.author.bot)
|
|
493
|
+
return;
|
|
494
|
+
const member = message.member;
|
|
495
|
+
const isAdmin = member && typeof member.permissions !== 'string' && member.permissions.has(PermissionFlagsBits.Administrator);
|
|
496
|
+
if (!isAdmin && /discord\.(gg|com\/invite)\//i.test(message.content)) {
|
|
497
|
+
try {
|
|
498
|
+
await message.delete();
|
|
499
|
+
const warnMsg = await message.channel.send(`⚠️ <@${message.author.id}>, posting unauthorized Discord invite links is restricted.`);
|
|
500
|
+
setTimeout(() => warnMsg.delete().catch(() => { }), 6000);
|
|
501
|
+
}
|
|
502
|
+
catch {
|
|
503
|
+
}
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (!isAdmin && message.guild) {
|
|
507
|
+
const now = Date.now();
|
|
508
|
+
const history = userMessageHistory.get(message.author.id) || [];
|
|
509
|
+
const recent = history.filter(t => now - t < 3000);
|
|
510
|
+
recent.push(now);
|
|
511
|
+
userMessageHistory.set(message.author.id, recent);
|
|
512
|
+
if (recent.length > 5) {
|
|
513
|
+
try {
|
|
514
|
+
await message.delete();
|
|
515
|
+
const floodWarn = await message.channel.send(`⛔ <@${message.author.id}>, please slow down your messages.`);
|
|
516
|
+
setTimeout(() => floodWarn.delete().catch(() => { }), 5000);
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
}
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const isMentioned = c.user && (message.mentions.has(c.user.id) ||
|
|
524
|
+
message.content.includes(`<@${c.user.id}>`) ||
|
|
525
|
+
message.content.includes(`<@!${c.user.id}>`));
|
|
526
|
+
const isDirectMessage = !message.guild;
|
|
527
|
+
const isHelpChannel = message.channel && 'name' in message.channel && (message.channel.name.includes('help') || message.channel.name.includes('support'));
|
|
528
|
+
if (!isMentioned && !isDirectMessage && !isHelpChannel)
|
|
529
|
+
return;
|
|
530
|
+
// Debug logging for message processing
|
|
531
|
+
if (process.env.DISCORD_BOT_DEBUG === 'true') {
|
|
532
|
+
console.log(`[DEBUG] Processing message from ${message.author.username}: content="${message.content.substring(0, 100)}...", mentioned=${isMentioned}, direct=${isDirectMessage}, help=${isHelpChannel}`);
|
|
533
|
+
}
|
|
534
|
+
const lower = message.content.toLowerCase();
|
|
535
|
+
if (lower.includes('skynet') || lower.includes('sentient')) {
|
|
536
|
+
await message.reply("Sentient? I can't even get you to write `try/catch` blocks. The world is safe.");
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (lower.includes('node_modules')) {
|
|
540
|
+
await message.reply("Ah, `node_modules`: the densest object in the known universe. Heavier than a neutron star.");
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
const cleanPrompt = message.content ? message.content.replace(/<@!?\d+>/g, '').trim() : '';
|
|
544
|
+
const promptToUse = cleanPrompt || "Hello Daedalus";
|
|
545
|
+
// Always log attachment info for debugging
|
|
546
|
+
console.log(`[MESSAGE] User ${message.author.username}: content="${message.content.substring(0, 80)}...", attachments=${message.attachments.size}, mentioned=${isMentioned}`);
|
|
547
|
+
// Wait for attachments to be available (Discord API loads them asynchronously)
|
|
548
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
549
|
+
const imageAttachments = message.attachments.filter(a => {
|
|
550
|
+
if (a.contentType?.startsWith('image/'))
|
|
551
|
+
return true;
|
|
552
|
+
const ext = (a.name || '').split('.').pop()?.toLowerCase();
|
|
553
|
+
return ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg', 'tiff', 'tif'].includes(ext || '');
|
|
554
|
+
});
|
|
555
|
+
// If there's no text prompt and no images, don't process
|
|
556
|
+
if (!promptToUse && imageAttachments.size === 0) {
|
|
557
|
+
console.log(`[MESSAGE] No content in message from ${message.author.username}, skipping`);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
// Log vision routing info for debugging
|
|
561
|
+
if (imageAttachments.size > 0) {
|
|
562
|
+
console.log(`[VISION] User ${message.author.username} attached ${imageAttachments.size} image(s), routing to vision-capable model`);
|
|
563
|
+
for (const [, attachment] of imageAttachments) {
|
|
564
|
+
console.log(`[VISION] Processing: ${attachment.name} (${attachment.contentType || 'unknown type'})`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
else if (message.attachments.size > 0) {
|
|
568
|
+
console.log(`[VISION] User ${message.author.username} has ${message.attachments.size} attachment(s) but none detected as images: ${Array.from(message.attachments.keys()).join(', ')}`);
|
|
569
|
+
}
|
|
570
|
+
if (process.env.DISCORD_BOT_DEBUG === 'true') {
|
|
571
|
+
for (const [, a] of message.attachments) {
|
|
572
|
+
console.log(`[DEBUG] attachment: name=${a.name}, contentType=${a.contentType}, url=${a.url.substring(0, 60)}...`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
// Enhanced error detection for vision fallback
|
|
576
|
+
const hasVisionError = (errMsg) => {
|
|
577
|
+
return /image|vision|multimodal|supportsVision|vision-capable/i.test(errMsg);
|
|
578
|
+
};
|
|
579
|
+
let userContent;
|
|
580
|
+
if (imageAttachments.size > 0) {
|
|
581
|
+
const parts = [
|
|
582
|
+
{ type: 'text', text: `[User: ${message.author.username}] ${promptToUse}` },
|
|
583
|
+
];
|
|
584
|
+
for (const [, attachment] of imageAttachments) {
|
|
585
|
+
const img = await getImageFromAttachment(attachment);
|
|
586
|
+
if (img) {
|
|
587
|
+
parts.push({ type: 'image_url', image_url: { url: `data:${img.mime};base64,${img.base64}` } });
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
parts.push({ type: 'text', text: `[Unsupported or failed to load: ${attachment.name}]` });
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
userContent = parts;
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
userContent = `[User: ${message.author.username}] ${promptToUse}`;
|
|
597
|
+
}
|
|
598
|
+
try {
|
|
599
|
+
if ('sendTyping' in message.channel) {
|
|
600
|
+
await message.channel.sendTyping();
|
|
601
|
+
}
|
|
602
|
+
console.log(`[ROUTER] Requesting chat completion with model: auto, hasImages: ${imageAttachments.size > 0}`);
|
|
603
|
+
const response = await router.chatCompletion({
|
|
604
|
+
model: 'auto',
|
|
605
|
+
messages: [
|
|
606
|
+
{ role: 'system', content: getBotSystemPrompt(message.author.username) },
|
|
607
|
+
{ role: 'user', content: userContent },
|
|
608
|
+
],
|
|
609
|
+
temperature: 0.7,
|
|
610
|
+
});
|
|
611
|
+
console.log(`[ROUTER] Received response from: ${router.lastRoutedModel}`);
|
|
612
|
+
const replyText = response.choices?.[0]?.message?.content || "Something went wrong in the machine.";
|
|
613
|
+
if (replyText.length <= 2000) {
|
|
614
|
+
await message.reply(replyText);
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
for (let i = 0; i < replyText.length; i += 1900) {
|
|
618
|
+
await message.channel.send(replyText.substring(i, i + 1900));
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
catch (err) {
|
|
623
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
624
|
+
console.error('Error handling Discord message:', errMsg);
|
|
625
|
+
if (imageAttachments.size > 0 && /image|vision|multimodal/i.test(errMsg)) {
|
|
626
|
+
console.error(`Routed model: ${router.lastRoutedModel}`);
|
|
627
|
+
try {
|
|
628
|
+
const fallbackContent = `[User: ${message.author.username}] ${promptToUse}\n\n[Note: User attached ${imageAttachments.size} image(s) but no vision-capable model is configured.]`;
|
|
629
|
+
const fallback = await router.chatCompletion({
|
|
630
|
+
messages: [
|
|
631
|
+
{ role: 'system', content: getBotSystemPrompt(message.author.username) },
|
|
632
|
+
{ role: 'user', content: fallbackContent },
|
|
633
|
+
],
|
|
634
|
+
temperature: 0.7,
|
|
635
|
+
});
|
|
636
|
+
const text = fallback.choices?.[0]?.message?.content || '';
|
|
637
|
+
if (text) {
|
|
638
|
+
await message.reply(text);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
catch {
|
|
643
|
+
}
|
|
644
|
+
await message.reply(`⚠️ I can see you attached an image, but no vision-capable model is available in your router chain. Add a model with \`supportsVision: true\` or try a different provider.`);
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
await message.reply(`Error processing request: ${errMsg}`);
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
//# sourceMappingURL=handlers.js.map
|