From 68f24d50e1d0ee3847d97e07cc2e686306d5b008 Mon Sep 17 00:00:00 2001 From: tanmay11k Date: Sun, 22 Feb 2026 13:34:26 -0500 Subject: [PATCH] Initial commit: Discord-Claude Gateway with event-driven agent runtime --- docs/FUTURE-FEATURES.md | 150 ++++++++++++++ docs/PROCESS-FLOW.md | 437 ++++++++++++++++++++++++++++++++++++++++ package-lock.json | 351 ++------------------------------ package.json | 1 + src/index.ts | 1 + 5 files changed, 602 insertions(+), 338 deletions(-) create mode 100644 docs/FUTURE-FEATURES.md create mode 100644 docs/PROCESS-FLOW.md diff --git a/docs/FUTURE-FEATURES.md b/docs/FUTURE-FEATURES.md new file mode 100644 index 0000000..0925281 --- /dev/null +++ b/docs/FUTURE-FEATURES.md @@ -0,0 +1,150 @@ +# Aetheel — Future Features Roadmap + +Features inspired by nanoclaw that can be implemented in the CLI-based gateway. Excludes Docker containers, WhatsApp channel, and Agent SDK (already ruled out). + +## Quick Wins (hours) + +### 1. Message History Storage + +Store inbound/outbound messages in a JSON file per channel. Nanoclaw uses SQLite, but a simple `config/messages/{channelId}.json` would work. This lets the agent see conversation history even after session expiry. + +How nanoclaw does it: SQLite table with id, chat_jid, sender, sender_name, content, timestamp, is_from_me, is_bot_message columns. Indexed by timestamp. + +Our approach: JSON file per channel at `config/messages/{channelId}.json`. Append-only. Include sender name, content, timestamp, and direction (inbound/outbound). Load recent messages into the system prompt or pass as context. + +### 2. Structured Logging + +Replace `console.log` with a proper logger (pino). Nanoclaw logs everything with structured context (group, duration, event type). Makes debugging way easier on a headless server. + +How nanoclaw does it: Pino logger with pretty-printing, configurable log levels via LOG_LEVEL env var, uncaught exception routing, container logs saved to disk. + +Our approach: Install pino, replace all console.log/error/warn calls, add log levels, save logs to `config/logs/`. Structured JSON logs make it easy to grep and filter. + +### 3. Conversation Archiving + +Before sessions get too long, archive transcripts to `config/conversations/`. Nanoclaw does this via a PreCompact hook that saves the full transcript as markdown before Claude compacts its context. + +Our approach: After each CLI response, optionally append the exchange (prompt + response) to `config/conversations/{channelId}/{date}.md`. This creates a human-readable conversation log separate from Claude's internal session storage. + +### 4. Retry with Backoff + +When the Claude CLI fails, retry with exponential backoff (5s, 10s, 20s, 40s, 80s) up to 5 times. Nanoclaw does this for container failures. + +How nanoclaw does it: GroupQueue tracks retryCount per group, uses `BASE_RETRY_MS * 2^retryCount` delay, max 5 retries, resets on success. + +Our approach: Wrap the `executeClaude` call in a retry loop. On transient errors (timeout, CLI crash), wait and retry. On permanent errors (auth failure, invalid session), fail immediately. + +## Medium Effort (day or two) + +### 5. Agent-Managed Tasks via File-Based IPC + +The big one from nanoclaw. The agent can create, pause, resume, and cancel scheduled tasks dynamically during a conversation. + +How nanoclaw does it: Custom MCP server with tools like `schedule_task`, `list_tasks`, `pause_task`, `resume_task`, `cancel_task`. Tasks stored in SQLite with schedule_type (cron/interval/once), context_mode (group/isolated), and full lifecycle tracking. + +Our approach (simpler): The agent writes task definitions to `config/tasks.json` using the Write tool. The gateway polls this file periodically and schedules/unschedules tasks accordingly. Task format: + +```json +[ + { + "id": "morning-briefing", + "prompt": "Good morning! Check email and summarize.", + "schedule_type": "cron", + "schedule_value": "0 9 * * *", + "status": "active", + "target_channel": "1475008084022788312" + } +] +``` + +The agent can add/remove/modify entries. The gateway watches the file and updates schedulers. + +### 6. Multi-Turn Message Batching + +Instead of sending one message at a time to Claude, batch multiple messages that arrive while the agent is processing. + +How nanoclaw does it: Collects all messages since the last agent run timestamp, formats them as XML (`content`), sends the batch as a single prompt. + +Our approach: When a message arrives while the agent is already processing for that channel, queue it. When the current processing finishes, batch all queued messages into a single prompt with sender/timestamp metadata. This prevents the agent from missing rapid-fire messages. + +### 7. Idle Timeout for Sessions + +Auto-clear sessions after a configurable period of inactivity per channel. + +How nanoclaw does it: 30-minute idle timeout per container. After last output, a timer starts. If no new messages arrive, the container is closed and the session ends. + +Our approach: Track `lastActivityTimestamp` per channel in the session manager. Run a periodic check (every 5 minutes) that removes sessions older than the timeout (default 30 min). This prevents stale sessions from accumulating and keeps memory.md relevant. + +### 8. Per-Channel Isolation + +Different channels could have different persona configs. A "work" channel gets a professional persona, a "fun" channel gets a casual one. + +How nanoclaw does it: Per-group folders with separate CLAUDE.md, sessions, memory, and container mounts. Each group is fully isolated. + +Our approach: Support a `config/channels/{channelId}/` override directory. If it exists, load persona files from there instead of the root config. Falls back to root config for missing files. This lets you customize identity.md or soul.md per channel. + +## Bigger Features (multi-day) + +### 9. IPC-Based Proactive Messaging + +Let the agent send messages to Discord channels proactively — not just as a response to a user message. + +How nanoclaw does it: Filesystem-based IPC. The agent writes a JSON file to `/workspace/ipc/messages/` with `{type: "message", chatJid: "...", text: "..."}`. The host polls the directory every second and sends the message. + +Our approach: Create a `config/ipc/outbound/` directory. The agent writes JSON files there using the Write tool. The gateway polls every 2 seconds, reads new files, sends messages to the specified Discord channel, and deletes the processed files. This enables: +- Agent sending follow-up messages after thinking +- Agent notifying you about something it found during a heartbeat +- Agent sending messages to channels it's not currently chatting in + +File format: +```json +{ + "type": "message", + "channelId": "1475008084022788312", + "text": "Hey, I found something interesting in your email!" +} +``` + +### 10. Skills Engine (Simplified) + +A system for adding capabilities via markdown skill files that get loaded into the system prompt. + +How nanoclaw does it: Full manifest-based system with YAML manifests, dependency resolution, conflict detection, file operations (rename, delete, move), backup/restore, rebase capability, and resolution caching. + +Our approach (much simpler): A `config/skills/` directory where each subdirectory contains a `SKILL.md` file. Skills are loaded into the system prompt as additional sections. The agent can reference skills by name. No dependency management — just markdown files that add context. + +``` +config/skills/ +├── web-research/ +│ └── SKILL.md → "When asked to research, use WebSearch and WebFetch..." +├── code-review/ +│ └── SKILL.md → "When reviewing code, focus on security, performance..." +└── email-helper/ + └── SKILL.md → "When checking email, use the Gmail API via..." +``` + +### 11. SQLite Storage + +Replace JSON files with SQLite for messages, sessions, tasks, and state. Better for concurrent access, querying, and doesn't corrupt on partial writes. + +How nanoclaw does it: Single SQLite database at `store/messages.db` with tables for chats, messages, scheduled_tasks, task_run_logs, router_state, sessions, registered_groups. Uses better-sqlite3 (synchronous API). + +Our approach: Install better-sqlite3, create a single `config/aetheel.db` with tables for sessions, messages, tasks, and state. Migrate existing JSON files on first run. + +### 12. Secrets Management + +Prevent API keys and sensitive data from leaking through agent tool use. + +How nanoclaw does it: Secrets passed via stdin (never written to disk), stripped from Bash subprocess environments via a PreToolUse hook, never mounted as files in containers. + +Our approach: Since we use `--dangerously-skip-permissions`, the agent can run Bash commands that might echo environment variables. Add a sanitization step: before passing the prompt to Claude, strip any env var values that look like secrets from the system prompt. Also consider using `--disallowedTools Bash` if the agent doesn't need shell access. + +## Priority Recommendation + +1. Structured logging (immediate debugging value) +2. Message history storage (conversation context) +3. Retry with backoff (reliability) +4. IPC-based proactive messaging (agent autonomy) +5. Agent-managed tasks (dynamic scheduling) +6. Conversation archiving (audit trail) +7. Everything else as needed diff --git a/docs/PROCESS-FLOW.md b/docs/PROCESS-FLOW.md new file mode 100644 index 0000000..d4d7575 --- /dev/null +++ b/docs/PROCESS-FLOW.md @@ -0,0 +1,437 @@ +# Aetheel — Process Flow + +How a Discord message becomes an AI response, step by step. + +## The Big Picture + +``` +Discord User Aetheel Gateway Claude Code CLI + │ │ │ + │ @Aetheel what's 2+2? │ │ + ├──────────────────────────────► │ │ + │ │ 1. Extract prompt "what's 2+2?" │ + │ │ 2. Check concurrency limit │ + │ │ 3. Enqueue message event │ + │ │ 4. Read config/*.md files │ + │ │ 5. Assemble system prompt │ + │ │ 6. Write prompt to temp file │ + │ │ 7. Spawn CLI process │ + │ │ │ + │ │ claude -p "what's 2+2?" │ + │ │ --output-format json │ + │ │ --append-system-prompt-file ... │ + │ │ --dangerously-skip-permissions │ + │ ├──────────────────────────────────► │ + │ │ │ + │ │ ◄── JSON stream (init, result) │ + │ │ ◄─────────────────────────────────┤ + │ │ │ + │ │ 8. Parse session_id from init │ + │ │ 9. Parse result text │ + │ │ 10. Split if > 2000 chars │ + │ "2 + 2 = 4" │ │ + │ ◄──────────────────────────────┤ │ + │ │ 11. Save session for channel │ +``` + +## Step-by-Step: Discord Message → Response + +### Step 1: Message Arrives in Discord + +A user types `@Aetheel what's the weather like?` in a Discord channel. + +Discord delivers this to the bot as a `messageCreate` event via the WebSocket gateway. + +**File:** `src/discord-bot.ts` → `setupMessageHandler()` + +``` +Raw message content: "<@1473096872372600978> what's the weather like?" +Author: tanmay11k6417 (bot: false) +Channel: 1475008084022788312 +``` + +### Step 2: Message Filtering & Prompt Extraction + +The bot checks: +1. Is the author a bot? → Skip (prevents feedback loops) +2. Does the message mention the bot? → Continue +3. Extract the prompt by stripping all mention tags + +**File:** `src/discord-bot.ts` → `extractPromptFromMention()` + +``` +Input: "<@1473096872372600978> what's the weather like?" +Output: "what's the weather like?" +``` + +The regex `/<@[!&]?\d+>/g` strips user mentions (`<@ID>`), nickname mentions (`<@!ID>`), and role mentions (`<@&ID>`). + +### Step 3: Prompt Handler (Gateway Core) + +The extracted prompt is wrapped in a `Prompt` object and passed to the gateway core's `onPrompt` handler. + +**File:** `src/gateway-core.ts` → `onPrompt` callback + +```typescript +{ + text: "what's the weather like?", + channelId: "1475008084022788312", + userId: "123456789", + guildId: "987654321" +} +``` + +The handler checks: +- Is the gateway shutting down? → Reply "Gateway is shutting down" +- Is `activeQueryCount >= maxConcurrentQueries` (default 5)? → Reply "System is busy" +- Otherwise: increment counter, send typing indicator, enqueue event + +### Step 4: Event Queue + +The prompt becomes a **message event** in the unified event queue. + +**File:** `src/event-queue.ts` + +```typescript +{ + id: 2, // Monotonically increasing + type: "message", + payload: { + prompt: { + text: "what's the weather like?", + channelId: "1475008084022788312", + userId: "123456789", + guildId: "987654321" + } + }, + timestamp: "2026-02-22T10:30:00.000Z", + source: "discord" +} +``` + +The queue processes events one at a time (FIFO). If a heartbeat or cron event is ahead in the queue, the message waits. + +### Step 5: Agent Runtime — Read Config Files + +When the event reaches the front of the queue, the Agent Runtime reads ALL markdown config files fresh from disk. + +**File:** `src/markdown-config-loader.ts` → `loadAll()` + +``` +config/ +├── identity.md → Agent name, role, vibe +├── soul.md → Personality, tone, values +├── agents.md → Operating rules, safety boundaries +├── user.md → Info about the human +├── memory.md → Long-term memory (agent can write to this) +└── tools.md → Tool configs, API notes +``` + +Files are read fresh every time — edit them while the gateway is running and the next event picks up changes. + +If `memory.md` doesn't exist, it's auto-created with `# Memory\n`. + +### Step 6: Assemble System Prompt + +The markdown file contents are concatenated into a single system prompt with section headers. + +**File:** `src/system-prompt-assembler.ts` → `assemble()` + +The assembled prompt looks like this: + +``` +You may update your long-term memory by writing to memory.md using the Write tool. +Use this to persist important facts, lessons learned, and context across sessions. + +## Identity + +# Identity + +- **Name:** Aetheel +- **Vibe:** Helpful, sharp, slightly witty +- **Emoji:** ⚡ + +## Personality + +# Soul + +Be genuinely helpful. Have opinions. Be resourceful before asking. +Keep responses concise for Discord. Use markdown formatting. + +## Operating Rules + +# Operating Rules + +Be helpful and concise. Keep Discord messages short. + +## Cron Jobs +... + +## User Context + +# User Context + +- **Name:** Tanmay +- **Timezone:** IST +... + +## Long-Term Memory + +# Memory + +- Tanmay prefers short responses +- Project aetheel-2 is the Discord gateway +... + +## Tool Configuration + +# Tool Configuration + +(empty or tool-specific notes) +``` + +Sections with null or empty content are omitted entirely. + +### Step 7: Write System Prompt to Temp File + +The assembled system prompt is written to a temporary file because it can be thousands of characters — too large for a CLI argument. + +**File:** `src/agent-runtime.ts` → `executeClaude()` + +``` +/tmp/aetheel-prompt-1d6c77f1-4a4e-49f8-ae9b-cff6fb47b971.txt +``` + +This file is deleted after the CLI process completes. + +### Step 8: Spawn Claude CLI + +The gateway spawns the Claude Code CLI as a child process. + +**File:** `src/agent-runtime.ts` → `runClaude()` + +The actual command: + +```bash +claude \ + -p "what's the weather like?" \ + --output-format json \ + --dangerously-skip-permissions \ + --append-system-prompt-file /tmp/aetheel-prompt-xxx.txt \ + --allowedTools Read \ + --allowedTools Write \ + --allowedTools Edit \ + --allowedTools Glob \ + --allowedTools Grep \ + --allowedTools WebSearch \ + --allowedTools WebFetch \ + --max-turns 25 +``` + +Key flags: +- `-p` — Print mode (non-interactive, exits after response) +- `--output-format json` — Returns JSON array of message objects +- `--dangerously-skip-permissions` — No interactive permission prompts +- `--append-system-prompt-file` — Appends our persona/memory to Claude's default prompt +- `--allowedTools` — Which tools Claude can use (one flag per tool) +- `--max-turns` — Prevents runaway agent loops +- `--resume SESSION_ID` — Added when resuming an existing conversation + +The process runs with `cwd` set to the `config/` directory, so Claude can read/write files there (like `memory.md`). + +`stdin` is set to `"ignore"` to prevent the CLI from waiting for interactive input. + +### Step 9: Session Resumption + +If this channel has chatted before, the session manager has a stored session ID. + +**File:** `src/session-manager.ts` + +``` +config/sessions.json: +{ + "1475008084022788312": "37336c32-73cb-4cf5-9771-1c8f694398ff" +} +``` + +When a session ID exists, `--resume 37336c32-73cb-4cf5-9771-1c8f694398ff` is added to the CLI args. Claude loads the full conversation history from `~/.claude/` and continues the conversation. + +### Step 10: Parse CLI Output (Streaming) + +The CLI returns a JSON array on stdout. The gateway parses it as chunks arrive. + +**File:** `src/agent-runtime.ts` → `runClaude()` stdout handler + +Example CLI output: + +```json +[ + { + "type": "system", + "subtype": "init", + "session_id": "37336c32-73cb-4cf5-9771-1c8f694398ff", + "tools": ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "WebSearch", "WebFetch"] + }, + { + "type": "assistant", + "message": { "content": [{ "type": "text", "text": "Let me check..." }] } + }, + { + "type": "result", + "subtype": "success", + "result": "I don't have access to real-time weather data, but I can help you check! Try asking me to search the web for current weather in your area.", + "session_id": "37336c32-73cb-4cf5-9771-1c8f694398ff", + "is_error": false, + "cost_usd": 0.003 + } +] +``` + +The parser extracts: +1. `session_id` from the `init` message → stored for future resumption +2. `result` from the `result` message → sent to Discord + +When streaming is active, result text is sent to Discord immediately as it's parsed, before the CLI process exits. + +### Step 11: Response Formatting & Delivery + +The result text is split into Discord-safe chunks (max 2000 characters each). + +**File:** `src/response-formatter.ts` → `splitMessage()` + +If the response contains code blocks that span a split boundary, the formatter closes the code block with ` ``` ` at the end of one chunk and reopens it with ` ``` ` at the start of the next. + +The chunks are sent sequentially to the Discord channel via the bot. + +### Step 12: Session Persistence + +The session ID is saved to `config/sessions.json` so it survives gateway restarts. + +**File:** `src/session-manager.ts` → `saveToDisk()` + +Next time the user sends a message in the same channel, the conversation continues from where it left off. + +--- + +## Other Event Types + +### Heartbeat Flow + +``` +Timer fires (every N seconds) + → HeartbeatScheduler creates heartbeat event + → Event enters queue + → AgentRuntime reads config files, assembles prompt + → CLI runs with heartbeat instruction as prompt + → Response sent to OUTPUT_CHANNEL_ID +``` + +Example heartbeat.md: +```markdown +## check-email +Interval: 1800 +Instruction: Check my inbox for anything urgent. If nothing, reply HEARTBEAT_OK. +``` + +The instruction becomes the `-p` argument to the CLI. + +### Cron Job Flow + +``` +Cron expression matches (e.g., "0 9 * * *" = 9am daily) + → CronScheduler creates cron event + → Event enters queue + → AgentRuntime reads config files, assembles prompt + → CLI runs with cron instruction as prompt + → Response sent to OUTPUT_CHANNEL_ID +``` + +Cron jobs are defined in `config/agents.md`: +```markdown +## Cron Jobs + +### morning-briefing +Cron: 0 9 * * * +Instruction: Good morning! Check email and give me a brief summary. +``` + +### Hook Flow + +``` +Lifecycle event occurs (startup, shutdown) + → HookManager creates hook event + → Event enters queue + → AgentRuntime reads config files, assembles prompt + → CLI runs with hook instruction as prompt + → Response sent to OUTPUT_CHANNEL_ID +``` + +Hooks are defined in `config/agents.md`: +```markdown +## Hooks + +### startup +Instruction: Say hello, you just came online. + +### shutdown +Instruction: Save important context to memory.md before shutting down. +``` + +`agent_begin` and `agent_stop` hooks fire inline (not through the queue) before and after every non-hook event. + +--- + +## What Gets Sent to Claude + +For every event, Claude receives: + +1. **Default Claude Code system prompt** (built-in, from the CLI) +2. **Appended system prompt** (from our assembled markdown files): + - Identity (who the agent is) + - Personality (how it behaves) + - Operating rules (safety, workflows) + - User context (who it's helping) + - Long-term memory (persistent facts) + - Tool configuration (API notes) + - Preamble about writing to memory.md +3. **The prompt text** (user message, heartbeat instruction, or cron instruction) +4. **Session history** (if resuming via `--resume`) +5. **Allowed tools** (Read, Write, Edit, Glob, Grep, WebSearch, WebFetch) + +Claude runs in the `config/` directory, so it can read and write files there — including updating `memory.md` with new facts. + +--- + +## File Map + +``` +src/ +├── index.ts ← Entry point: creates GatewayCore, registers shutdown handler +├── gateway-core.ts ← Orchestrator: wires everything, manages lifecycle +├── config.ts ← Reads env vars (DISCORD_BOT_TOKEN, etc.) +├── discord-bot.ts ← Discord.js wrapper: messages, slash commands, typing +├── event-queue.ts ← FIFO queue: all events (message, heartbeat, cron, hook) +├── agent-runtime.ts ← Core engine: reads configs, spawns CLI, parses output +├── markdown-config-loader.ts ← Reads config/*.md files fresh each event +├── system-prompt-assembler.ts ← Concatenates markdown into system prompt with headers +├── session-manager.ts ← Channel → session ID mapping (persisted to JSON) +├── response-formatter.ts ← Splits long text for Discord's 2000 char limit +├── error-formatter.ts ← Sanitizes errors (strips keys, paths, stacks) +├── heartbeat-scheduler.ts ← setInterval timers from heartbeat.md +├── cron-scheduler.ts ← node-cron jobs from agents.md +├── hook-manager.ts ← Lifecycle hooks from agents.md +├── bootstrap-manager.ts ← First-run: validates/creates config files +├── channel-queue.ts ← Per-channel sequential processing +└── shutdown-handler.ts ← SIGTERM/SIGINT → graceful shutdown + +config/ +├── identity.md ← Agent name, role, specialization +├── soul.md ← Personality, tone, values +├── agents.md ← Rules, cron jobs, hooks +├── user.md ← Human's info and preferences +├── memory.md ← Long-term memory (agent-writable) +├── tools.md ← Tool configs and notes +├── heartbeat.md ← Proactive check definitions +├── boot.md ← Bootstrap parameters (optional) +└── sessions.json ← Channel → session ID map (auto-generated) +``` diff --git a/package-lock.json b/package-lock.json index e29817f..4c832f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.2.50", "discord.js": "^14.25.1", + "dotenv": "^17.3.1", "node-cron": "^4.2.1" }, "devDependencies": { @@ -22,29 +22,6 @@ "vitest": "^4.0.18" } }, - "node_modules/@anthropic-ai/claude-agent-sdk": { - "version": "0.2.50", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.50.tgz", - "integrity": "sha512-zVQzJbicfTmvS6uarFQYYVYiYedKE0FgXmhiGC1oSLm6OkIbuuKM7XV4fXEFxPZHcWQc7ZYv6HA2/P5HOE7b2Q==", - "license": "SEE LICENSE IN README.md", - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "^0.34.2", - "@img/sharp-darwin-x64": "^0.34.2", - "@img/sharp-linux-arm": "^0.34.2", - "@img/sharp-linux-arm64": "^0.34.2", - "@img/sharp-linux-x64": "^0.34.2", - "@img/sharp-linuxmusl-arm64": "^0.34.2", - "@img/sharp-linuxmusl-x64": "^0.34.2", - "@img/sharp-win32-arm64": "^0.34.2", - "@img/sharp-win32-x64": "^0.34.2" - }, - "peerDependencies": { - "zod": "^4.0.0" - } - }, "node_modules/@discordjs/builders": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz", @@ -617,310 +594,6 @@ "node": ">=18" } }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -1545,6 +1218,18 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/dotenv": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -2210,16 +1895,6 @@ "optional": true } } - }, - "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } } } diff --git a/package.json b/package.json index 1941777..cb4cc11 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "license": "MIT", "dependencies": { "discord.js": "^14.25.1", + "dotenv": "^17.3.1", "node-cron": "^4.2.1" }, "devDependencies": { diff --git a/src/index.ts b/src/index.ts index c937bea..25f1d60 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +import "dotenv/config"; import { GatewayCore } from "./gateway-core.js"; import { registerShutdownHandler } from "./shutdown-handler.js";