Skip to content

Commit a637cd2

Browse files
AdametherzLabclaude
andcommitted
v0.3.0: Add gauge, stats, sparkWithStatus, dashboard for LCD/IoT/AI agents
4 new features broadening appeal beyond DevOps: - gauge(value, max) — progress/level bar for LCD, battery, tank fill - stats(values) — min/max/avg/median/stdDev/percentiles with summary string - sparkWithStatus(values, thresholds) — threshold-aware sparkline with emoji/color/status - dashboard(metrics) — multi-metric compact display, full and compact modes 16 new tests (28 pass total), updated README with use case examples, added keywords (lcd, oled, iot, ai-agent, dashboard, arduino, raspberry-pi). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d26265a commit a637cd2

6 files changed

Lines changed: 568 additions & 162 deletions

File tree

README.md

Lines changed: 165 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,202 +1,211 @@
1-
# webhook-spark
1+
# webhook-spark
22

3-
**Send minimalist homelab alerts with ASCII sparklines to Discord/Slack**
3+
**ASCII sparklines, gauges, dashboards & threshold alerts for Discord/Slack/Telegram, LCD screens, IoT & AI agents.**
44

5-
## ✨ Features
5+
Zero dependencies. TypeScript first. Under 15KB.
66

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
7+
## Who is this for?
138

14-
## 📦 Installation
9+
- **Homelab / DevOps** -- server monitoring alerts with sparklines and threshold status
10+
- **LCD / OLED hackers** -- fixed-width output that fits 16x2, 20x4, and SSD1306 displays
11+
- **DIY / IoT makers** -- multi-sensor dashboards for greenhouses, aquariums, server racks
12+
- **AI agent builders** -- compact metric summaries that fit in LLM context windows
13+
14+
## Installation
1515

1616
```bash
17-
# Using Bun (recommended)
18-
bun add webhook-spark
17+
bun add @adametherzlab/webhook-spark
18+
# or: npm install @adametherzlab/webhook-spark
19+
```
1920

20-
# Using npm
21-
npm install webhook-spark
21+
## Quick Start
2222

23-
# Using yarn
24-
yarn add webhook-spark
23+
```typescript
24+
import { spark, gauge, stats, sparkWithStatus, dashboard, sendWebhook } from '@adametherzlab/webhook-spark';
25+
26+
// Sparkline from numbers
27+
spark([10, 25, 60, 85, 90, 45, 30]);
28+
// => "▁▂▅▇█▃▂"
29+
30+
// Progress gauge (battery, tank level, task completion)
31+
gauge(75, 100);
32+
// => "████████████████░░░░ 75%"
33+
34+
// Summary statistics
35+
stats([10, 20, 30, 40, 50]).summary;
36+
// => "min=10 max=50 avg=30 p95=48"
37+
38+
// Threshold-aware sparkline
39+
sparkWithStatus([45, 50, 62, 78, 95], { warning: 70, critical: 90 });
40+
// => { sparkline: "▂▃▅▆█", status: "critical", emoji: "🔴", ... }
41+
42+
// Multi-metric dashboard in one call
43+
dashboard([
44+
{ name: 'CPU', values: [45,50,62,78], unit: '%', thresholds: { warning: 70, critical: 90 } },
45+
{ name: 'MEM', values: [78,80,82,85], unit: '%', thresholds: { warning: 80, critical: 95 } },
46+
{ name: 'DISK', values: [62,63,63,64], unit: '%', thresholds: { warning: 85, critical: 95 } },
47+
]);
48+
// =>
49+
// CPU 78% ▂▃▅█ ⚠️
50+
// MEM 85% ▅▆▇█ ⚠️
51+
// DISK 64% ▅▅▅▅ ✅
2552
```
2653

27-
## 🚀 Quick Start
54+
## API Reference
55+
56+
### `spark(values)` -- Simple Sparkline
2857

2958
```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: ▁▂▅▇██▃▂
59+
spark([1, 5, 2, 8, 3, 7]); // => "▁▅▂█▃▆"
4660
```
4761

48-
## 📖 API Reference
62+
### `gauge(value, max, options?)` -- Progress / Level Gauge
63+
64+
```typescript
65+
gauge(75, 100) // => "████████████████░░░░ 75%"
66+
gauge(3.7, 4.2, { label: "BATT" }) // => "BATT ██████████████████░░ 88%"
67+
gauge(6, 20, { width: 10, fill: "#", empty: "." }) // => "###....... 30%"
68+
69+
// Threshold alerts
70+
gauge(92, 100, { thresholds: { warning: 70, critical: 90 } })
71+
// => "████████████████████ 92% CRITICAL"
72+
```
4973

50-
### Sparkline Generation
74+
**LCD use case:** `lcd.print(gauge(sensorVal, 1023, { width: 16 }))` -- fits a 16-char LCD line.
75+
76+
Options: `width` (default 20), `fill` (default ``), `empty` (default ``), `showPercent` (default true), `showValue`, `label`, `thresholds`.
77+
78+
### `stats(values, options?)` -- Summary Statistics
5179

5280
```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-
);
81+
const s = stats([10, 20, 30, 40, 50]);
82+
// s.min=10, s.max=50, s.avg=30, s.median=30, s.stdDev=14.14
83+
// s.percentiles = { 95: 48 }
84+
// s.summary = "min=10 max=50 avg=30 p95=48"
85+
86+
// Custom percentiles
87+
stats(data, { percentiles: [50, 90, 99], decimals: 1 });
7188
```
7289

73-
### Webhook Delivery
90+
**AI agent use case:** `stats([...tokenCosts]).summary` -- one-line data summary an LLM can reason about.
91+
92+
### `sparkWithStatus(values, thresholds)` -- Threshold-Aware Sparkline
7493

7594
```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-
});
95+
sparkWithStatus([45, 50, 62, 78, 95], { warning: 70, critical: 90 })
96+
// => {
97+
// sparkline: "▂▃▅▆█",
98+
// status: "critical",
99+
// emoji: "🔴",
100+
// color: 0xe74c3c, // Discord embed color
101+
// breachCount: 2,
102+
// breachPercent: 40
103+
// }
104+
105+
// Inverted mode: low values are bad (disk space, battery)
106+
sparkWithStatus([15, 10, 5, 3], { warning: 10, critical: 5, invert: true })
107+
// => status: "critical"
101108
```
102109

103-
### Type Utilities
110+
Discord embed color auto-maps: green (ok) / yellow (warning) / red (critical).
111+
112+
### `dashboard(metrics, options?)` -- Multi-Metric Display
104113

105114
```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-
}
115+
// Full mode (with sparklines)
116+
dashboard([
117+
{ name: 'CPU', values: [45,50,62,78], unit: '%', thresholds: { warning: 70, critical: 90 } },
118+
{ name: 'MEM', values: [78,80,82,85], unit: '%', thresholds: { warning: 80, critical: 95 } },
119+
{ name: 'DISK', values: [62,63,63,64], unit: '%', thresholds: { warning: 85, critical: 95 } },
120+
{ name: 'TEMP', values: [42,44,45,43], unit: '°C', thresholds: { warning: 60, critical: 75 } },
121+
]);
122+
// CPU 78% ▂▃▅█ ⚠️
123+
// MEM 85% ▅▆▇█ ⚠️
124+
// DISK 64% ▅▅▅▅ ✅
125+
// TEMP 43°C ▃▄▅▃ ✅
126+
127+
// Compact mode (for 20x4 LCD or AI context)
128+
dashboard([...], { compact: true });
129+
// CPU 78% ⚠️
130+
// MEM 85% ⚠️
131+
// DISK 64% ✅
132+
// TEMP 43°C ✅
119133
```
120134

121-
## 🧪 Examples
135+
**LCD use case:** Render to 20x4 or 128x64 OLED in one call.
136+
**AI agent use case:** Paste entire system status into context in 4 lines.
122137

123-
### Homelab CPU Monitor
138+
### `barChart(entries, options?)` -- Horizontal Bar Chart
124139

125140
```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-
}
141+
barChart([
142+
{ label: 'GET', value: 150 },
143+
{ label: 'POST', value: 80 },
144+
{ label: 'PUT', value: 30 },
145+
], { maxBarWidth: 15 });
146+
// GET ███████████████ 150
147+
// POST ████████ 80
148+
// PUT ███ 30
150149
```
151150

152-
### Daily Health Report
151+
### `trend(values, window?)` -- Trend Arrow
153152

154153
```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-
});
154+
trend([10, 20, 30]); // => "↑"
155+
trend([30, 20, 10]); // => "↓"
156+
trend([10, 10, 10]); // => "→"
173157
```
174158

175-
## 🔧 Configuration
159+
### `sendWebhook(payload, config)` -- Webhook Delivery
160+
161+
Supports Discord, Slack, and Telegram webhooks with validation, retry, and timeout.
176162

177-
```json
178-
{
179-
"defaultWebhook": "https://discord.com/api/webhooks/your-id",
180-
"defaultProvider": "discord",
181-
"dataDir": "~/.webhook-spark/data",
182-
"maxEntries": 1000
183-
}
163+
```typescript
164+
await sendWebhook(
165+
{ timestamp: new Date(), metricName: 'cpu', sparkline: spark(cpuData), rawValues: cpuData },
166+
{ endpoint: 'https://discord.com/api/webhooks/...', provider: 'discord' }
167+
);
168+
```
169+
170+
### `generateSparkline(data, options)` -- Advanced Sparkline
171+
172+
Full control: custom character sets, interpolation, axis, outlier detection.
173+
174+
## Use Case Examples
175+
176+
### IoT Greenhouse Dashboard
177+
178+
```typescript
179+
const sensors = [
180+
{ name: 'SOIL', values: moistureReadings, unit: '%', thresholds: { warning: 30, critical: 15, invert: true } },
181+
{ name: 'TEMP', values: tempReadings, unit: '°C', thresholds: { warning: 35, critical: 40 } },
182+
{ name: 'HUM', values: humidityReadings, unit: '%' },
183+
];
184+
console.log(dashboard(sensors));
184185
```
185186

186-
## 🤝 Contributing
187+
### AI Agent System Prompt
187188

188-
Found a bug? Have an idea for a new feature? Contributions are welcome!
189+
```typescript
190+
const status = dashboard([
191+
{ name: 'Tokens', values: tokenHistory, unit: 'K', thresholds: { warning: 80, critical: 95 } },
192+
{ name: 'Tasks', values: taskCounts, thresholds: { warning: 50, critical: 100 } },
193+
], { compact: true });
194+
// Inject into system prompt: 2 lines, minimal tokens
195+
```
189196

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
197+
### Arduino LCD (16x2)
198+
199+
```typescript
200+
const line1 = gauge(analogRead(A0), 1023, { width: 16, showPercent: false });
201+
const line2 = `T:${temp}C ${trend(tempHistory)}`;
202+
lcd.print(line1 + '\n' + line2);
203+
```
195204

196-
## 📄 License
205+
## License
197206

198-
MIT © AdametherzLab
207+
MIT
199208

200209
---
201210

202-
Made with ⚡ by homelab enthusiasts for homelab enthusiasts. Keep your servers happy and your alerts beautiful!
211+
Built for homelabs, hackerspaces, and AI agents.

package.json

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@adametherzlab/webhook-spark",
3-
"version": "0.2.0",
4-
"description": "Send minimalist homelab alerts with ASCII sparklines to Discord/Slack/Telegram.",
3+
"version": "0.3.0",
4+
"description": "ASCII sparklines, gauges, dashboards & threshold alerts for Discord/Slack/Telegram, LCD screens, IoT & AI agents.",
55
"type": "module",
66
"main": "src/index.ts",
77
"module": "src/index.ts",
@@ -31,7 +31,15 @@
3131
"discord-webhook",
3232
"telegram-bot",
3333
"sparkline",
34-
"cli"
34+
"cli",
35+
"lcd",
36+
"oled",
37+
"iot",
38+
"ai-agent",
39+
"dashboard",
40+
"gauge",
41+
"arduino",
42+
"raspberry-pi"
3543
],
3644
"author": "AdametherzLab",
3745
"license": "MIT",

0 commit comments

Comments
 (0)