Something went wrong. Try again.
personal experiment with a claw
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130import { Client, Events, GatewayIntentBits, Partials, type Message,} from "discord.js";import { createSharedDeps, describeModel } from "./agent.js";import { LaneManager } from "./lanes.js";import { PresenceIndicator, idlePresence } from "./presence.js";import { Scheduler } from "./scheduler.js";import { TypingIndicator } from "./typing.js";import { chunkForDiscord } from "./util.js";
const TOKEN = process.env.DISCORD_BOT_TOKEN;const ALLOWED = (process.env.DISCORD_ALLOWED_USER_IDS ?? "") .split(",") .map((s) => s.trim()) .filter(Boolean);
if (!TOKEN) { console.error("DISCORD_BOT_TOKEN not set. Copy .env.example to .env and fill it in."); process.exit(1);}if (ALLOWED.length === 0) { console.error( "DISCORD_ALLOWED_USER_IDS not set. Refusing to start — would respond to anyone." ); process.exit(1);}
function extractContent(msg: Message, mentioned: boolean): string { const raw = mentioned ? msg.content.replace(/<@!?\d+>/g, "") : msg.content; return raw.trim();}
async function main() { console.log(`disclaw starting (${describeModel()}), logging in…`); const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.DirectMessages, ], partials: [Partials.Channel, Partials.Message], presence: idlePresence(), });
const presence = new PresenceIndicator(client); const typing = new TypingIndicator(client); let scheduler: Scheduler | null = null; const deps = await createSharedDeps({ onSchedulesChanged: () => scheduler?.scheduleReload(), }); const lanes = new LaneManager(deps, client); scheduler = new Scheduler({ deps, client, schedulesDir: deps.schedulesDir, presence, });
client.once(Events.ClientReady, async (c) => { console.log(`disclaw bot ready as ${c.user.tag} (${describeModel()})`); console.log(`allowed user IDs: ${ALLOWED.join(", ")}`); presence.refresh(); await scheduler!.start(); });
client.on(Events.MessageCreate, async (msg) => { if (msg.author.bot) return; if (!ALLOWED.includes(msg.author.id)) return;
const isDm = msg.channel.isDMBased(); const mentioned = client.user ? msg.mentions.has(client.user.id) : false; if (!isDm && !mentioned) return;
const content = extractContent(msg, mentioned); if (!content) return;
const channel = msg.channel; await typing.start(msg.channelId);
try { const result = await presence.track( () => lanes.send(msg.channelId, content, { userId: msg.author.id }) ); if (result.steered) { await msg.react("⏳").catch(() => {}); } else if (!("send" in channel)) { console.error("channel has no send method", channel); } else { const parts = chunkForDiscord(result.output); if (parts.length === 0) { await channel.send("(empty response)"); } else { for (const part of parts) { await channel.send(part); } } } } catch (err) { console.error("disclaw bot error:", err); if ("send" in channel) { channel.send("⚠️ error — check logs").catch(() => {}); } } finally { typing.stop(msg.channelId); } });
await client.login(TOKEN);
const shutdown = () => { console.log("\nshutting down…"); scheduler?.stop(); client.destroy(); lanes.dispose(); process.exit(0); }; process.on("SIGINT", shutdown); process.on("SIGTERM", shutdown);}
main().catch((err) => { console.error(err); process.exit(1);});