|
| 1 | +# webhook-spark ⚡ |
| 2 | + |
| 3 | +**Send minimalist homelab alerts with ASCII sparklines to Discord/Slack** |
| 4 | + |
| 5 | +## ✨ Features |
| 6 | + |
| 7 | +- 📊 **Beautiful ASCII sparklines** – Turn boring number arrays into visual trends |
| 8 | +- 🔌 **Webhook support** – Discord and Slack out of the box |
| 9 | +- 🚀 **Zero dependencies** – Uses only Node.js/Bun built-ins |
| 10 | +- 📦 **Tiny footprint** – Less than 10KB minified |
| 11 | +- 🛡️ **TypeScript first** – Full type safety and autocomplete |
| 12 | +- 🎨 **Customizable** – Multiple character sets and styling options |
| 13 | + |
| 14 | +## 📦 Installation |
| 15 | + |
| 16 | +```bash |
| 17 | +# Using Bun (recommended) |
| 18 | +bun add webhook-spark |
| 19 | + |
| 20 | +# Using npm |
| 21 | +npm install webhook-spark |
| 22 | + |
| 23 | +# Using yarn |
| 24 | +yarn add webhook-spark |
| 25 | +``` |
| 26 | + |
| 27 | +## 🚀 Quick Start |
| 28 | + |
| 29 | +```typescript |
| 30 | +// REMOVED external import: import { generateSparkline, sendWebhook } from 'webhook-spark'; |
| 31 | + |
| 32 | +// Create a sparkline from your metrics |
| 33 | +const cpuUsage = [10, 25, 60, 85, 90, 45, 30]; |
| 34 | +const sparkline = generateSparkline(cpuUsage); |
| 35 | + |
| 36 | +// Send to Discord |
| 37 | +await sendWebhook({ |
| 38 | + url: 'https://discord.com/api/webhooks/your-webhook-id', |
| 39 | + provider: 'discord', |
| 40 | + content: `CPU usage: ${sparkline}`, |
| 41 | + username: 'Server Monitor' |
| 42 | +}); |
| 43 | + |
| 44 | +console.log(`📈 Sparkline sent: ${sparkline}`); |
| 45 | +// Output: 📈 Sparkline sent: ▁▂▅▇██▃▂ |
| 46 | +``` |
| 47 | + |
| 48 | +## 📖 API Reference |
| 49 | + |
| 50 | +### Sparkline Generation |
| 51 | + |
| 52 | +```typescript |
| 53 | +// REMOVED external import: import { generateSparkline, generateSparklineWithOutliers } from 'webhook-spark'; |
| 54 | + |
| 55 | +// Basic sparkline |
| 56 | +const sparkline = generateSparkline([1, 2, 3, 4, 5]); |
| 57 | +// Returns: ▁▂▃▄▅ |
| 58 | + |
| 59 | +// With custom options |
| 60 | +const custom = generateSparkline([10, 50, 90], { |
| 61 | + characterSet: '·∙●○◉◎', |
| 62 | + minValue: 0, |
| 63 | + maxValue: 100 |
| 64 | +}); |
| 65 | + |
| 66 | +// Handle outliers |
| 67 | +const withOutliers = generateSparklineWithOutliers( |
| 68 | + [1, 1000, 2, 3, 4], |
| 69 | + { threshold: 2 } // Values > 2 standard deviations marked |
| 70 | +); |
| 71 | +``` |
| 72 | + |
| 73 | +### Webhook Delivery |
| 74 | + |
| 75 | +```typescript |
| 76 | +// REMOVED external import: import { sendWebhook } from 'webhook-spark'; |
| 77 | + |
| 78 | +// Discord example |
| 79 | +await sendWebhook({ |
| 80 | + url: 'DISCORD_WEBHOOK_URL', |
| 81 | + provider: 'discord', |
| 82 | + content: 'Server alert!', |
| 83 | + embeds: [{ |
| 84 | + title: 'CPU Usage', |
| 85 | + description: generateSparkline([10, 25, 60, 85]), |
| 86 | + color: 0xff0000, |
| 87 | + timestamp: new Date().toISOString() |
| 88 | + }] |
| 89 | +}); |
| 90 | + |
| 91 | +// Slack example |
| 92 | +await sendWebhook({ |
| 93 | + url: 'SLACK_WEBHOOK_URL', |
| 94 | + provider: 'slack', |
| 95 | + content: 'Daily metrics', |
| 96 | + blocks: [{ |
| 97 | + type: 'section', |
| 98 | + text: { type: 'mrkdwn', text: `*Memory usage:* ${generateSparkline([30, 45, 60])}` } |
| 99 | + }] |
| 100 | +}); |
| 101 | +``` |
| 102 | + |
| 103 | +### Type Utilities |
| 104 | + |
| 105 | +```typescript |
| 106 | +// REMOVED external import: import { isNumericArray, isWebhookConfig } from 'webhook-spark'; |
| 107 | + |
| 108 | +// Type guards for validation |
| 109 | +if (isNumericArray(data)) { |
| 110 | + // data is now typed as readonly number[] |
| 111 | + const sparkline = generateSparkline(data); |
| 112 | +} |
| 113 | + |
| 114 | +const config = { url: '...', provider: 'discord' }; |
| 115 | +if (isWebhookConfig(config)) { |
| 116 | + // config is valid WebhookConfig |
| 117 | + await sendWebhook(config); |
| 118 | +} |
| 119 | +``` |
| 120 | + |
| 121 | +## 🧪 Examples |
| 122 | + |
| 123 | +### Homelab CPU Monitor |
| 124 | + |
| 125 | +```typescript |
| 126 | +// REMOVED external import: import { generateSparkline, sendWebhook } from 'webhook-spark'; |
| 127 | +import os from 'os'; |
| 128 | + |
| 129 | +// Simulate collecting CPU metrics |
| 130 | +const cpuMetrics = [45, 60, 75, 85, 90, 80, 65]; |
| 131 | +const sparkline = generateSparkline(cpuMetrics); |
| 132 | + |
| 133 | +// Send alert if high usage |
| 134 | +if (cpuMetrics[cpuMetrics.length - 1] > 80) { |
| 135 | + await sendWebhook({ |
| 136 | + url: process.env.DISCORD_WEBHOOK!, |
| 137 | + provider: 'discord', |
| 138 | + content: `🚨 High CPU usage detected!`, |
| 139 | + embeds: [{ |
| 140 | + title: 'CPU Trend', |
| 141 | + description: `\`${sparkline}\``, |
| 142 | + fields: [ |
| 143 | + { name: 'Current', value: `${cpuMetrics[cpuMetrics.length - 1]}%`, inline: true }, |
| 144 | + { name: 'Peak', value: `${Math.max(...cpuMetrics)}%`, inline: true } |
| 145 | + ], |
| 146 | + color: 0xff5500 |
| 147 | + }] |
| 148 | + }); |
| 149 | +} |
| 150 | +``` |
| 151 | + |
| 152 | +### Daily Health Report |
| 153 | + |
| 154 | +```typescript |
| 155 | +// REMOVED external import: import { generateSparkline, generateASCIIArt } from 'webhook-spark'; |
| 156 | + |
| 157 | +// Create a dashboard-like report |
| 158 | +const report = ` |
| 159 | +📊 **Daily System Report** |
| 160 | +━━━━━━━━━━━━━━━━━━━━ |
| 161 | +CPU: ${generateSparkline([10, 25, 40, 60, 45, 30])} |
| 162 | +Memory: ${generateSparkline([50, 55, 60, 65, 70, 68])} |
| 163 | +Disk: ${generateSparkline([85, 86, 87, 88, 89, 90])} |
| 164 | +
|
| 165 | +${generateASCIIArt('HEALTHY', { font: 'block' })} |
| 166 | +`; |
| 167 | + |
| 168 | +await sendWebhook({ |
| 169 | + url: process.env.SLACK_WEBHOOK!, |
| 170 | + provider: 'slack', |
| 171 | + content: report |
| 172 | +}); |
| 173 | +``` |
| 174 | + |
| 175 | +## 🔧 Configuration |
| 176 | + |
| 177 | +```json |
| 178 | +{ |
| 179 | + "defaultWebhook": "https://discord.com/api/webhooks/your-id", |
| 180 | + "defaultProvider": "discord", |
| 181 | + "dataDir": "~/.webhook-spark/data", |
| 182 | + "maxEntries": 1000 |
| 183 | +} |
| 184 | +``` |
| 185 | + |
| 186 | +## 🤝 Contributing |
| 187 | + |
| 188 | +Found a bug? Have an idea for a new feature? Contributions are welcome! |
| 189 | + |
| 190 | +1. Fork the repository |
| 191 | +2. Create a feature branch (`git checkout -b cool-new-feature`) |
| 192 | +3. Commit your changes (`git commit -am 'Add cool feature'`) |
| 193 | +4. Push to the branch (`git push origin cool-new-feature`) |
| 194 | +5. Open a Pull Request |
| 195 | + |
| 196 | +## 📄 License |
| 197 | + |
| 198 | +MIT © AdametherzLab |
| 199 | + |
| 200 | +--- |
| 201 | + |
| 202 | +Made with ⚡ by homelab enthusiasts for homelab enthusiasts. Keep your servers happy and your alerts beautiful! |
0 commit comments