Skip to content

Commit d5f5789

Browse files
AdametherzLabclaude
andcommitted
v0.4.0: Add kaomoji, heatmaps, tables, histograms & comparisons
5 new features: kaomoji mood system (5 themes x 13 moods), 2D heatmap grids, box-drawing tables (miniTable/kvTable), frequency histograms, and side-by-side compare with delta/percentage/direction. 33 new tests, full README rewrite. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a637cd2 commit d5f5789

6 files changed

Lines changed: 977 additions & 100 deletions

File tree

README.md

Lines changed: 188 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
# webhook-spark
22

3-
**ASCII sparklines, gauges, dashboards & threshold alerts for Discord/Slack/Telegram, LCD screens, IoT & AI agents.**
3+
**Zero-dep terminal sparklines, gauges, kaomoji, heatmaps, tables, histograms & dashboards for Discord/Slack/Telegram, LCD screens, IoT & AI agents.**
44

5-
Zero dependencies. TypeScript first. Under 15KB.
5+
Zero dependencies. TypeScript first. Under 20KB.
66

77
## Who is this for?
88

9-
- **Homelab / DevOps** -- server monitoring alerts with sparklines and threshold status
9+
- **Homelab / DevOps** -- server monitoring alerts with sparklines, heatmaps, and threshold status
1010
- **LCD / OLED hackers** -- fixed-width output that fits 16x2, 20x4, and SSD1306 displays
1111
- **DIY / IoT makers** -- multi-sensor dashboards for greenhouses, aquariums, server racks
1212
- **AI agent builders** -- compact metric summaries that fit in LLM context windows
13+
- **Dashboard addicts** -- kaomoji status faces, histograms, comparison charts, all in pure text
1314

1415
## Installation
1516

@@ -21,34 +22,32 @@ bun add @adametherzlab/webhook-spark
2122
## Quick Start
2223

2324
```typescript
24-
import { spark, gauge, stats, sparkWithStatus, dashboard, sendWebhook } from '@adametherzlab/webhook-spark';
25+
import { spark, gauge, kaomoji, kaomojiStatus, heatmap, dashboard } from '@adametherzlab/webhook-spark';
2526

2627
// Sparkline from numbers
2728
spark([10, 25, 60, 85, 90, 45, 30]);
2829
// => "▁▂▅▇█▃▂"
2930

30-
// Progress gauge (battery, tank level, task completion)
31+
// Progress gauge
3132
gauge(75, 100);
3233
// => "████████████████░░░░ 75%"
3334

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% ▅▅▅▅ ✅
35+
// Kaomoji mood faces
36+
kaomoji("happy"); // => "(*^▽^*)"
37+
kaomoji("happy", { theme: "cats" }); // => "ᓚᘏᗢ"
38+
39+
// Status kaomoji from a value
40+
kaomojiStatus(95, 100, { thresholds: { warning: 70, critical: 90 } });
41+
// => { face: "(╥_╥)", mood: "critical" }
42+
43+
// Heatmap grid
44+
heatmap([[2,5,8],[1,7,9],[3,6,7]], {
45+
showLabels: true, rowLabels: ["Mon","Tue","Wed"], colLabels: ["AM","PM","NT"]
46+
});
47+
// => AM PM NT
48+
// Mon ░ ▒ ▓
49+
// Tue ░ ▓ █
50+
// Wed ░ ▒ ▓
5251
```
5352

5453
## API Reference
@@ -64,134 +63,222 @@ spark([1, 5, 2, 8, 3, 7]); // => "▁▅▂█▃▆"
6463
```typescript
6564
gauge(75, 100) // => "████████████████░░░░ 75%"
6665
gauge(3.7, 4.2, { label: "BATT" }) // => "BATT ██████████████████░░ 88%"
67-
gauge(6, 20, { width: 10, fill: "#", empty: "." }) // => "###....... 30%"
68-
69-
// Threshold alerts
7066
gauge(92, 100, { thresholds: { warning: 70, critical: 90 } })
7167
// => "████████████████████ 92% CRITICAL"
7268
```
7369

74-
**LCD use case:** `lcd.print(gauge(sensorVal, 1023, { width: 16 }))` -- fits a 16-char LCD line.
70+
Options: `width`, `fill`, `empty`, `showPercent`, `showValue`, `label`, `thresholds`.
7571

76-
Options: `width` (default 20), `fill` (default ``), `empty` (default ``), `showPercent` (default true), `showValue`, `label`, `thresholds`.
72+
### `kaomoji(mood, options?)` -- Mood Kaomoji
7773

78-
### `stats(values, options?)` -- Summary Statistics
74+
Returns a kaomoji face for the given mood. 5 themes, 13 moods.
7975

8076
```typescript
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"
77+
kaomoji("happy") // => "(*^▽^*)"
78+
kaomoji("happy", { theme: "cats" }) // => "ᓚᘏᗢ"
79+
kaomoji("critical", { theme: "bears" }) // => "ʕ×ᴥ×ʔ"
80+
kaomoji("celebrating") // => "☆*:.。.o(≧▽≦)o.。.:*☆"
81+
kaomoji("sleeping", { theme: "minimal" }) // => "-_-zzz"
82+
```
83+
84+
**Moods:** `happy`, `ok`, `warning`, `critical`, `sad`, `angry`, `love`, `surprised`, `sleeping`, `working`, `celebrating`, `confused`, `dead`
85+
86+
**Themes:** `classic`, `cats`, `bears`, `stars`, `minimal`
8587

86-
// Custom percentiles
87-
stats(data, { percentiles: [50, 90, 99], decimals: 1 });
88+
### `kaomojiAll(mood, options?)` -- All Faces for a Mood
89+
90+
```typescript
91+
kaomojiAll("happy");
92+
// => ["(*^▽^*)", "(´。• ᵕ •。`)", "(✿◠‿◠)", "(❁´◡`❁)"]
8893
```
8994

90-
**AI agent use case:** `stats([...tokenCosts]).summary` -- one-line data summary an LLM can reason about.
95+
### `kaomojiStatus(value, max, options?)` -- Value-to-Mood Mapping
9196

92-
### `sparkWithStatus(values, thresholds)` -- Threshold-Aware Sparkline
97+
Maps a numeric value to a mood face based on thresholds. Compose with dashboard output.
98+
99+
```typescript
100+
kaomojiStatus(50, 100) // => { face: "(・_・)", mood: "ok" }
101+
kaomojiStatus(95, 100, { thresholds: { warning: 70, critical: 90 } })
102+
// => { face: "(╥_╥)", mood: "critical" }
103+
104+
// Compose with other metrics:
105+
`CPU 85% ${kaomojiStatus(85, 100).face}` // => "CPU 85% (・_・;)"
106+
```
107+
108+
### `kaomojiThemes()` -- List Available Themes
109+
110+
```typescript
111+
kaomojiThemes(); // => ["classic", "cats", "bears", "stars", "minimal"]
112+
```
113+
114+
### `heatmap(data, options?)` -- 2D Grid Heatmap
115+
116+
GitHub contribution graph style, pure text. Rows x columns of shade characters.
117+
118+
```typescript
119+
heatmap([[2,5,8,3],[1,7,9,4],[3,6,7,2]], {
120+
showLabels: true,
121+
rowLabels: ["Mon","Tue","Wed"],
122+
colLabels: ["00","06","12","18"]
123+
});
124+
// => 00 06 12 18
125+
// Mon ░ ▒ ▓ ░
126+
// Tue ░ ▓ █ ▒
127+
// Wed ░ ▒ ▓ ░
128+
```
129+
130+
Options: `chars` (default `[" ","░","▒","▓","█"]`), `showLabels`, `rowLabels`, `colLabels`, `min`, `max`.
131+
132+
### `miniTable(rows, options?)` -- Compact Box-Drawing Table
133+
134+
4 border styles: `single`, `double`, `rounded`, `none`.
135+
136+
```typescript
137+
miniTable([["Metric","Value","Status"],["CPU","78%","OK"]], { header: true, border: "rounded" });
138+
// => ╭────────┬───────┬────────╮
139+
// │ Metric │ Value │ Status │
140+
// ├────────┼───────┼────────┤
141+
// │ CPU │ 78% │ OK │
142+
// ╰────────┴───────┴────────╯
143+
```
144+
145+
Options: `border`, `align` (per-column), `header`, `compact`, `maxWidth`.
146+
147+
### `kvTable(entries)` -- Key-Value Table
148+
149+
```typescript
150+
kvTable([{key:"CPU",value:"78%"},{key:"MEM",value:"4.2GB"}]);
151+
// => ┌─────┬───────┐
152+
// │ CPU │ 78% │
153+
// │ MEM │ 4.2GB │
154+
// └─────┴───────┘
155+
```
156+
157+
### `histogram(values, options?)` -- Frequency Distribution
158+
159+
Groups values into bins, renders horizontal bar chart of frequencies.
160+
161+
```typescript
162+
histogram([1,1,2,2,2,3,3,3,3,4,5,5,5,5,5], { bins: 5 });
163+
// => 1.0-1.8 ████████ 2
164+
// 1.8-2.6 ████████████ 3
165+
// 2.6-3.4 ████████████████ 4
166+
// 3.4-4.2 ████ 1
167+
// 4.2-5.0 ████████████████████ 5
168+
169+
histogram(latencies, { bins: 10, percentages: true, fill: "#" });
170+
```
171+
172+
Options: `bins` (default 8), `barWidth` (default 20), `showCounts`, `fill`, `showBounds`, `percentages`.
173+
174+
### `compare(label1, val1, label2, val2, options?)` -- Side-by-Side Comparison
175+
176+
Visual before/after with delta, percentage change, and direction arrow.
93177

94178
```typescript
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-
// }
179+
compare("Before", 45, "After", 78);
180+
// => { display: "Before ██████████████████ 45\nAfter ██████████████████████████████ 78\n↑ +33 (+73.3%)",
181+
// delta: 33, deltaPercent: 73.3, direction: "up", arrow: "↑" }
104182

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"
183+
compare("Plan", 100, "Actual", 87, { mode: "compact", unit: "%" });
184+
// => { display: "Plan 100% vs Actual 87% ↓-13 (-13.0%)", ... }
185+
186+
compare("Week1", [10,20,30], "Week2", [15,25,35], { mode: "spark" });
187+
// => { display: "Week1 ▁▅█ avg=20.0\nWeek2 ▁▅█ avg=25.0\n↑ +5 (+25.0%)", ... }
188+
```
189+
190+
Options: `barWidth`, `showDelta`, `showPercent`, `unit`, `mode` (`"bars"` | `"spark"` | `"compact"`).
191+
192+
### `stats(values, options?)` -- Summary Statistics
193+
194+
```typescript
195+
stats([10, 20, 30, 40, 50]).summary;
196+
// => "min=10 max=50 avg=30 p95=48"
108197
```
109198

110-
Discord embed color auto-maps: green (ok) / yellow (warning) / red (critical).
199+
### `sparkWithStatus(values, thresholds)` -- Threshold-Aware Sparkline
200+
201+
```typescript
202+
sparkWithStatus([45, 50, 62, 78, 95], { warning: 70, critical: 90 });
203+
// => { sparkline: "▂▃▅▆█", status: "critical", emoji: "🔴", color: 0xe74c3c, breachCount: 2 }
204+
```
111205

112206
### `dashboard(metrics, options?)` -- Multi-Metric Display
113207

114208
```typescript
115-
// Full mode (with sparklines)
116209
dashboard([
117210
{ name: 'CPU', values: [45,50,62,78], unit: '%', thresholds: { warning: 70, critical: 90 } },
118211
{ name: 'MEM', values: [78,80,82,85], unit: '%', thresholds: { warning: 80, critical: 95 } },
119212
{ 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 } },
121213
]);
122214
// CPU 78% ▂▃▅█ ⚠️
123215
// MEM 85% ▅▆▇█ ⚠️
124216
// 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 ✅
133217
```
134218

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.
137-
138219
### `barChart(entries, options?)` -- Horizontal Bar Chart
139220

140221
```typescript
141222
barChart([
142223
{ label: 'GET', value: 150 },
143224
{ label: 'POST', value: 80 },
144-
{ label: 'PUT', value: 30 },
145225
], { maxBarWidth: 15 });
146-
// GET ███████████████ 150
147-
// POST ████████ 80
148-
// PUT ███ 30
149226
```
150227

151228
### `trend(values, window?)` -- Trend Arrow
152229

153230
```typescript
154231
trend([10, 20, 30]); // => "↑"
155232
trend([30, 20, 10]); // => "↓"
156-
trend([10, 10, 10]); // => "→"
157233
```
158234

159235
### `sendWebhook(payload, config)` -- Webhook Delivery
160236

161237
Supports Discord, Slack, and Telegram webhooks with validation, retry, and timeout.
162238

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
239+
## Use Case Gallery
175240

176241
### IoT Greenhouse Dashboard
177242

178243
```typescript
179244
const sensors = [
180245
{ 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: '%' },
246+
{ name: 'TEMP', values: tempReadings, unit: 'C', thresholds: { warning: 35, critical: 40 } },
183247
];
184-
console.log(dashboard(sensors));
248+
const status = dashboard(sensors);
249+
const face = kaomojiStatus(moistureReadings.at(-1), 100, { theme: "cats" }).face;
250+
console.log(`${status}\n${face}`);
185251
```
186252

187253
### AI Agent System Prompt
188254

189255
```typescript
190256
const status = dashboard([
191257
{ name: 'Tokens', values: tokenHistory, unit: 'K', thresholds: { warning: 80, critical: 95 } },
192-
{ name: 'Tasks', values: taskCounts, thresholds: { warning: 50, critical: 100 } },
193258
], { compact: true });
194-
// Inject into system prompt: 2 lines, minimal tokens
259+
const face = kaomojiStatus(tokenHistory.at(-1), 100).face;
260+
// => "Tokens 82K ⚠️ (・_・;)" -- 1 line, minimal tokens
261+
```
262+
263+
### Server Monitoring with Heatmap
264+
265+
```typescript
266+
// 7-day x 24-hour load heatmap
267+
const weeklyLoad = [ /* 7 arrays of 24 hourly values */ ];
268+
console.log(heatmap(weeklyLoad, {
269+
showLabels: true,
270+
rowLabels: ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],
271+
}));
272+
```
273+
274+
### Performance Comparison
275+
276+
```typescript
277+
const result = compare("v1.2", responseTimesOld, "v1.3", responseTimesNew, { mode: "spark", unit: "ms" });
278+
console.log(result.display);
279+
// v1.2 ▃▅▇█▆▅ avg=245.0
280+
// v1.3 ▂▃▅▆▄▃ avg=189.0
281+
// ↓ -56 (-22.9%)
195282
```
196283

197284
### Arduino LCD (16x2)
@@ -202,6 +289,22 @@ const line2 = `T:${temp}C ${trend(tempHistory)}`;
202289
lcd.print(line1 + '\n' + line2);
203290
```
204291

292+
## Why webhook-spark?
293+
294+
| Feature | webhook-spark | blessed-contrib | cli-table3 | ink |
295+
|---------|:---:|:---:|:---:|:---:|
296+
| Zero deps | Yes | No (17) | No (4) | No (11) |
297+
| Sparklines | Yes | Yes | No | No |
298+
| Gauges | Yes | Yes | No | No |
299+
| Kaomoji | Yes | No | No | No |
300+
| Heatmaps | Yes | Yes | No | No |
301+
| Tables | Yes | No | Yes | No |
302+
| Histograms | Yes | Yes | No | No |
303+
| Comparisons | Yes | No | No | No |
304+
| Webhooks | Yes | No | No | No |
305+
| Bundle size | <20KB | 2.5MB | 180KB | 400KB |
306+
| Maintained | Yes | No | Minimal | Yes |
307+
205308
## License
206309

207310
MIT

0 commit comments

Comments
 (0)