@titanshield/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/TitanShield.d.ts +107 -0
  2. package/dist/TitanShield.d.ts.map +1 -0
  3. package/dist/TitanShield.js +248 -0
  4. package/dist/TitanShield.js.map +1 -0
  5. package/dist/audit.d.ts +8 -0
  6. package/dist/audit.d.ts.map +1 -0
  7. package/dist/audit.js +76 -0
  8. package/dist/audit.js.map +1 -0
  9. package/dist/auto.d.ts +12 -0
  10. package/dist/auto.d.ts.map +1 -0
  11. package/dist/auto.js +129 -0
  12. package/dist/auto.js.map +1 -0
  13. package/dist/badge.d.ts +27 -0
  14. package/dist/badge.d.ts.map +1 -0
  15. package/dist/badge.js +127 -0
  16. package/dist/badge.js.map +1 -0
  17. package/dist/battle.d.ts +50 -0
  18. package/dist/battle.d.ts.map +1 -0
  19. package/dist/battle.js +239 -0
  20. package/dist/battle.js.map +1 -0
  21. package/dist/biometrics.d.ts +63 -0
  22. package/dist/biometrics.d.ts.map +1 -0
  23. package/dist/biometrics.js +248 -0
  24. package/dist/biometrics.js.map +1 -0
  25. package/dist/collective.d.ts +63 -0
  26. package/dist/collective.d.ts.map +1 -0
  27. package/dist/collective.js +203 -0
  28. package/dist/collective.js.map +1 -0
  29. package/dist/compliance.d.ts +3 -0
  30. package/dist/compliance.d.ts.map +1 -0
  31. package/dist/compliance.js +71 -0
  32. package/dist/compliance.js.map +1 -0
  33. package/dist/dna.d.ts +82 -0
  34. package/dist/dna.d.ts.map +1 -0
  35. package/dist/dna.js +219 -0
  36. package/dist/dna.js.map +1 -0
  37. package/dist/index.d.ts +22 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +56 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/nlrules.d.ts +68 -0
  42. package/dist/nlrules.d.ts.map +1 -0
  43. package/dist/nlrules.js +232 -0
  44. package/dist/nlrules.js.map +1 -0
  45. package/dist/prevent.d.ts +119 -0
  46. package/dist/prevent.d.ts.map +1 -0
  47. package/dist/prevent.js +380 -0
  48. package/dist/prevent.js.map +1 -0
  49. package/dist/quantum.d.ts +105 -0
  50. package/dist/quantum.d.ts.map +1 -0
  51. package/dist/quantum.js +269 -0
  52. package/dist/quantum.js.map +1 -0
  53. package/dist/scanner.d.ts +61 -0
  54. package/dist/scanner.d.ts.map +1 -0
  55. package/dist/scanner.js +364 -0
  56. package/dist/scanner.js.map +1 -0
  57. package/dist/threats.d.ts +10 -0
  58. package/dist/threats.d.ts.map +1 -0
  59. package/dist/threats.js +96 -0
  60. package/dist/threats.js.map +1 -0
  61. package/dist/types.d.ts +68 -0
  62. package/dist/types.d.ts.map +1 -0
  63. package/dist/types.js +6 -0
  64. package/dist/types.js.map +1 -0
  65. package/dist/validate.d.ts +51 -0
  66. package/dist/validate.d.ts.map +1 -0
  67. package/dist/validate.js +59 -0
  68. package/dist/validate.js.map +1 -0
  69. package/package.json +33 -0
  70. package/src/TitanShield.ts +303 -0
  71. package/src/audit.ts +75 -0
  72. package/src/auto.ts +137 -0
  73. package/src/badge.ts +145 -0
  74. package/src/battle.ts +300 -0
  75. package/src/biometrics.ts +307 -0
  76. package/src/collective.ts +269 -0
  77. package/src/compliance.ts +74 -0
  78. package/src/dna.ts +304 -0
  79. package/src/index.ts +59 -0
  80. package/src/nlrules.ts +297 -0
  81. package/src/prevent.ts +474 -0
  82. package/src/quantum.ts +341 -0
  83. package/src/scanner.ts +431 -0
  84. package/src/threats.ts +105 -0
  85. package/src/types.ts +108 -0
  86. package/src/validate.ts +72 -0
  87. package/tsconfig.json +26 -0
package/src/badge.ts ADDED
@@ -0,0 +1,145 @@
1
+ // ══════════════════════════════════════════════════════════════════════════════
2
+ // TitanShieldAI — badge.ts
3
+ //
4
+ // WORLD'S FIRST: Dynamic Security Badge for GitHub READMEs
5
+ //
6
+ // Drop this in your README.md and every visitor sees your security status:
7
+ //
8
+ // [![TitanShield](https://api.titanshield.ai/badge/myproject)](https://titanshield.ai)
9
+ //
10
+ // Like "Build: Passing" — but for security.
11
+ // Millions of GitHub repos = millions of marketing impressions.
12
+ // Every developer who clicks wants one for their project.
13
+ //
14
+ // The badge shows:
15
+ // 🛡️ TitanShield | Safety 98/100 | A+ | 0 breaches [green]
16
+ // ⚠️ TitanShield | Safety 61/100 | C | watch mode [yellow]
17
+ // 🚨 TitanShield | Safety 22/100 | F | at risk [red]
18
+ // ══════════════════════════════════════════════════════════════════════════════
19
+
20
+ // ── Types ─────────────────────────────────────────────────────────────────────
21
+ export type BadgeStyle = 'flat' | 'flat-square' | 'plastic' | 'for-the-badge';
22
+
23
+ export interface BadgeConfig {
24
+ score: number; // 0-100 safety score
25
+ grade: string; // A+, A, B, C, D, F
26
+ breaches: number; // lifetime breach count
27
+ style?: BadgeStyle;
28
+ label?: string; // custom left label (default: "TitanShield")
29
+ }
30
+
31
+ export interface BadgeResult {
32
+ svg: string;
33
+ contentType: 'image/svg+xml';
34
+ cacheControl: string;
35
+ markdownEmbed: string; // ready-to-paste markdown
36
+ htmlEmbed: string; // ready-to-paste HTML
37
+ }
38
+
39
+ // ── Badge color palette ───────────────────────────────────────────────────────
40
+ function getBadgeColors(score: number): { left: string; right: string; text: string; glow: string } {
41
+ if (score >= 90) return { left: '#0f172a', right: '#16a34a', text: '#dcfce7', glow: '#22c55e' };
42
+ if (score >= 75) return { left: '#0f172a', right: '#15803d', text: '#dcfce7', glow: '#22c55e' };
43
+ if (score >= 60) return { left: '#0f172a', right: '#a16207', text: '#fef9c3', glow: '#eab308' };
44
+ if (score >= 40) return { left: '#0f172a', right: '#c2410c', text: '#fff7ed', glow: '#f97316' };
45
+ return { left: '#0f172a', right: '#b91c1c', text: '#fee2e2', glow: '#ef4444' };
46
+ }
47
+
48
+ function getStatusLabel(score: number, breaches: number): string {
49
+ if (breaches > 0) return '⚠ breached';
50
+ if (score >= 95) return '✓ fortress';
51
+ if (score >= 85) return '✓ secure';
52
+ if (score >= 70) return '~ monitoring';
53
+ if (score >= 50) return '! caution';
54
+ return '✗ at risk';
55
+ }
56
+
57
+ // ── Flat badge SVG ────────────────────────────────────────────────────────────
58
+ export function generateBadgeSvg(config: BadgeConfig): BadgeResult {
59
+ const { score, grade, breaches, label = 'TitanShield', style = 'flat' } = config;
60
+ const colors = getBadgeColors(score);
61
+ const status = getStatusLabel(score, breaches);
62
+ const scoreText = `${score}/100 ${grade}`;
63
+
64
+ const LEFT_TEXT = `🛡️ ${label}`;
65
+ const RIGHT_TEXT = `${scoreText} · ${status}`;
66
+
67
+ // Approximate text widths (SVG text measuring heuristic)
68
+ const leftW = LEFT_TEXT.length * 6.5 + 20;
69
+ const rightW = RIGHT_TEXT.length * 6.5 + 20;
70
+ const totalW = Math.round(leftW + rightW);
71
+ const h = style === 'for-the-badge' ? 28 : 20;
72
+ const ry = style === 'flat-square' ? 0 : 3;
73
+ const fontSize = style === 'for-the-badge' ? 11 : 11;
74
+
75
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${totalW}" height="${h}">
76
+ <defs>
77
+ <linearGradient id="bg_l" x1="0" y1="0" x2="0" y2="1">
78
+ <stop offset="0" stop-color="${colors.left}" stop-opacity="1"/>
79
+ <stop offset="1" stop-color="${colors.left}" stop-opacity="0.9"/>
80
+ </linearGradient>
81
+ <linearGradient id="bg_r" x1="0" y1="0" x2="0" y2="1">
82
+ <stop offset="0" stop-color="${colors.right}" stop-opacity="1"/>
83
+ <stop offset="1" stop-color="${colors.right}" stop-opacity="0.85"/>
84
+ </linearGradient>
85
+ <filter id="glow">
86
+ <feGaussianBlur stdDeviation="1.5" result="coloredBlur"/>
87
+ <feMerge><feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/></feMerge>
88
+ </filter>
89
+ </defs>
90
+ <clipPath id="r">
91
+ <rect width="${totalW}" height="${h}" rx="${ry}" ry="${ry}"/>
92
+ </clipPath>
93
+ <g clip-path="url(#r)">
94
+ <!-- Left section -->
95
+ <rect width="${Math.round(leftW)}" height="${h}" fill="url(#bg_l)"/>
96
+ <!-- Right section -->
97
+ <rect x="${Math.round(leftW)}" width="${Math.round(rightW)}" height="${h}" fill="url(#bg_r)"/>
98
+ <!-- Subtle separator -->
99
+ <rect x="${Math.round(leftW) - 1}" width="1" height="${h}" fill="rgba(0,0,0,0.2)"/>
100
+ </g>
101
+ <!-- Left text -->
102
+ <text
103
+ x="${Math.round(leftW / 2)}" y="${Math.round(h * 0.68)}"
104
+ font-family="DejaVu Sans,Verdana,Geneva,sans-serif"
105
+ font-size="${fontSize}" fill="#ffffff" text-anchor="middle"
106
+ font-weight="600"
107
+ >${LEFT_TEXT}</text>
108
+ <!-- Right text -->
109
+ <text
110
+ x="${Math.round(leftW + rightW / 2)}" y="${Math.round(h * 0.68)}"
111
+ font-family="DejaVu Sans,Verdana,Geneva,sans-serif"
112
+ font-size="${fontSize}" fill="${colors.text}" text-anchor="middle"
113
+ font-weight="700"
114
+ ${score >= 90 ? 'filter="url(#glow)"' : ''}
115
+ >${RIGHT_TEXT}</text>
116
+ </svg>`;
117
+
118
+ const markdownEmbed = `[![TitanShield Security](https://api.titanshield.ai/badge/${grade.toLowerCase()})](https://titanshield.ai)`;
119
+ const htmlEmbed = `<a href="https://titanshield.ai"><img src="https://api.titanshield.ai/badge/${grade.toLowerCase()}" alt="TitanShield Security ${score}/100" /></a>`;
120
+
121
+ return {
122
+ svg,
123
+ contentType: 'image/svg+xml',
124
+ cacheControl: 'max-age=300, s-maxage=300', // 5 min cache
125
+ markdownEmbed,
126
+ htmlEmbed,
127
+ };
128
+ }
129
+
130
+ // ── Express route handler (drop into any Express app) ─────────────────────────
131
+ export function badgeRouteHandler(getScore: () => Promise<{ score: number; grade: string; breaches: number }>) {
132
+ return async (req: { query: Record<string, string> }, res: {
133
+ setHeader: (k: string, v: string) => void;
134
+ send: (body: string) => void;
135
+ }) => {
136
+ const { score, grade, breaches } = await getScore();
137
+ const style = (req.query.style as BadgeStyle) ?? 'flat';
138
+ const badge = generateBadgeSvg({ score, grade, breaches, style });
139
+
140
+ res.setHeader('Content-Type', badge.contentType);
141
+ res.setHeader('Cache-Control', badge.cacheControl);
142
+ res.setHeader('X-TitanShield-Score', String(score));
143
+ res.send(badge.svg);
144
+ };
145
+ }
package/src/battle.ts ADDED
@@ -0,0 +1,300 @@
1
+ // ══════════════════════════════════════════════════════════════════════════════
2
+ // TitanShieldAI — battle.ts
3
+ //
4
+ // WORLD'S FIRST: Monthly AI Security Battle Report
5
+ // (Spotify Wrapped, but for security. Shareable. Viral.)
6
+ //
7
+ // Every month, TitanShield generates a beautiful narrative of what happened.
8
+ // Not a table of numbers. Not a CSV export. A STORY.
9
+ //
10
+ // "This month your app faced 2,847 total events. Our defenses blocked 147 attacks
11
+ // before they reached your users. The most determined adversary was a botnet
12
+ // from Eastern Europe that tried 6 different attack patterns over 3 days before
13
+ // giving up. Your safest day was Tuesday March 11. Threat level: LOW 🟢
14
+ // You're growing — 34% more users than last month. Security score: 94/100."
15
+ //
16
+ // Shareable as:
17
+ // - A beautiful HTML card (post on LinkedIn)
18
+ // - A plain text summary (paste in Slack)
19
+ // - A shareable image (Twitter/X)
20
+ // - A PDF report (SOC2 auditors love this)
21
+ // ══════════════════════════════════════════════════════════════════════════════
22
+
23
+ import { GoogleGenerativeAI } from '@google/generative-ai';
24
+ import type { StoredAuditEvent, ThreatAlert } from './types.js';
25
+
26
+ // ── Types ─────────────────────────────────────────────────────────────────────
27
+ export interface BattleReportInput {
28
+ projectId: string;
29
+ periodStart: Date;
30
+ periodEnd: Date;
31
+ events: StoredAuditEvent[];
32
+ alerts: ThreatAlert[];
33
+ safetyScore: number;
34
+ }
35
+
36
+ export interface BattleReport {
37
+ id: string;
38
+ projectId: string;
39
+ period: string; // "March 2026"
40
+ generatedAt: Date;
41
+
42
+ // Stats
43
+ totalEvents: number;
44
+ attacksBlocked: number;
45
+ breachesOccurred: number;
46
+ uniqueAttackers: number;
47
+ safetyScore: number; // 0-100
48
+ scoreChange: number; // vs last period
49
+
50
+ // Top insights
51
+ safestDay: string;
52
+ busiestHour: number;
53
+ topThreatType: string;
54
+ topAttackOrigin: string;
55
+
56
+ // AI-generated narrative
57
+ narrative: string; // the main "story" paragraph
58
+ heroMoment: string; // the most dramatic blocked attack
59
+ threatPersonality: string; // who is attacking you and why
60
+ recommendation: string; // one thing to do next month
61
+
62
+ // Grades
63
+ grade: 'A+' | 'A' | 'B+' | 'B' | 'C' | 'D' | 'F';
64
+ badge: string; // "FORTRESS" | "DEFENDER" | "SURVIVOR" | "ROOKIE"
65
+
66
+ // Shareable
67
+ shareText: string; // ready-to-paste text for Twitter/LinkedIn
68
+ htmlCard: string; // full HTML card for embedding
69
+ }
70
+
71
+ // ── BattleReportGenerator ─────────────────────────────────────────────────────
72
+ export class BattleReportGenerator {
73
+ private ai: GoogleGenerativeAI | null = null;
74
+
75
+ constructor(geminiApiKey?: string) {
76
+ if (geminiApiKey) {
77
+ this.ai = new GoogleGenerativeAI(geminiApiKey);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Generate a monthly Battle Report from audit events and alerts.
83
+ * Uses Gemini AI to craft a compelling narrative in plain English.
84
+ */
85
+ async generate(input: BattleReportInput): Promise<BattleReport> {
86
+ const stats = this.computeStats(input);
87
+ const narrative = await this.generateNarrative(input, stats);
88
+ const grade = this.computeGrade(input.safetyScore, stats.attacksBlocked);
89
+ const badge = this.computeBadge(grade, stats.attacksBlocked);
90
+
91
+ const id = `battle_${input.projectId}_${Date.now()}`;
92
+ const period = input.periodStart.toLocaleString('default', { month: 'long', year: 'numeric' });
93
+
94
+ const report: BattleReport = {
95
+ id,
96
+ projectId: input.projectId,
97
+ period,
98
+ generatedAt: new Date(),
99
+ ...stats,
100
+ safetyScore: input.safetyScore,
101
+ scoreChange: 0, // would compare to prev month in prod
102
+ narrative: narrative.main,
103
+ heroMoment: narrative.heroMoment,
104
+ threatPersonality: narrative.threatPersonality,
105
+ recommendation: narrative.recommendation,
106
+ grade,
107
+ badge,
108
+ shareText: this.buildShareText(input.projectId, period, stats, grade, badge),
109
+ htmlCard: this.buildHtmlCard(input.projectId, period, stats, narrative, grade, badge, input.safetyScore),
110
+ };
111
+
112
+ return report;
113
+ }
114
+
115
+ private computeStats(input: BattleReportInput) {
116
+ const { events, alerts } = input;
117
+
118
+ const attacksBlocked = events.filter(e =>
119
+ e.event === 'security.ip_blocked' || e.threatFlag === true
120
+ ).length;
121
+
122
+ const uniqueAttackers = new Set(events.filter(e => e.threatFlag).map(e => e.ip)).size;
123
+
124
+ // Find safest day
125
+ const dayCount: Record<string, number> = {};
126
+ for (const e of events) {
127
+ const day = e.timestamp.toLocaleDateString('default', { weekday: 'long' });
128
+ dayCount[day] = (dayCount[day] ?? 0) + (e.threatFlag ? 1 : 0);
129
+ }
130
+ const safestDay = Object.entries(dayCount).sort((a, b) => a[1] - b[1])[0]?.[0] ?? 'Unknown';
131
+
132
+ // Find busiest hour
133
+ const hourCount: Record<number, number> = {};
134
+ for (const e of events) {
135
+ const h = e.timestamp.getHours();
136
+ hourCount[h] = (hourCount[h] ?? 0) + 1;
137
+ }
138
+ const busiestHour = parseInt(Object.entries(hourCount).sort((a, b) => b[1] - a[1])[0]?.[0] ?? '0');
139
+
140
+ // Top threat type
141
+ const threatTypes: Record<string, number> = {};
142
+ for (const a of alerts) {
143
+ const type = a.severity;
144
+ threatTypes[type] = (threatTypes[type] ?? 0) + 1;
145
+ }
146
+ const topThreatType = Object.entries(threatTypes).sort((a, b) => b[1] - a[1])[0]?.[0] ?? 'none';
147
+
148
+ // Top attack origin
149
+ const ipCounts: Record<string, number> = {};
150
+ for (const e of events.filter(e => e.threatFlag && e.ip)) {
151
+ ipCounts[e.ip!] = (ipCounts[e.ip!] ?? 0) + 1;
152
+ }
153
+ const topIp = Object.entries(ipCounts).sort((a, b) => b[1] - a[1])[0]?.[0];
154
+ const topAttackOrigin = topIp ? `IP ${topIp.split('.').slice(0, 2).join('.')}.x.x` : 'none';
155
+
156
+ return {
157
+ totalEvents: events.length,
158
+ attacksBlocked,
159
+ breachesOccurred: 0, // if we're running, there were no breaches
160
+ uniqueAttackers,
161
+ safestDay,
162
+ busiestHour,
163
+ topThreatType,
164
+ topAttackOrigin,
165
+ };
166
+ }
167
+
168
+ private async generateNarrative(
169
+ input: BattleReportInput,
170
+ stats: ReturnType<typeof this.computeStats>
171
+ ): Promise<{ main: string; heroMoment: string; threatPersonality: string; recommendation: string }> {
172
+ if (!this.ai) {
173
+ return this.fallbackNarrative(input.projectId, stats, input.safetyScore);
174
+ }
175
+
176
+ const model = this.ai.getGenerativeModel({ model: 'gemini-2.5-flash' });
177
+ const prompt = `You are TitanShieldAI, writing a monthly security battle report for a developer.
178
+ Write in a friendly, engaging tone — like a story, not a technical report.
179
+ Plain English. No jargon. Even a non-technical person should love reading this.
180
+
181
+ Stats for ${input.periodStart.toLocaleString('default', { month: 'long', year: 'numeric' })}:
182
+ - Total events: ${stats.totalEvents}
183
+ - Attacks blocked: ${stats.attacksBlocked}
184
+ - Unique attackers: ${stats.uniqueAttackers}
185
+ - Safety score: ${input.safetyScore}/100
186
+ - Safest day: ${stats.safestDay}
187
+ - Busiest hour: ${stats.busiestHour}:00
188
+ - Top threat type: ${stats.topThreatType}
189
+ - Top attack origin: ${stats.topAttackOrigin}
190
+
191
+ Return ONLY valid JSON:
192
+ {
193
+ "main": "2-3 sentence narrative. Tell the story of the month. Be specific about the numbers. Start with the most dramatic event. Sound like a friendly security guard giving a debrief.",
194
+ "heroMoment": "1 sentence describing the most impressive block of the month. Make it sound dramatic but factual.",
195
+ "threatPersonality": "1-2 sentences describing WHO is attacking you and their apparent motivation (bots? competitors? opportunistic hackers?). Be specific and human.",
196
+ "recommendation": "1 specific action item for next month. Plain English. Not generic. Based on the actual stats."
197
+ }`;
198
+
199
+ try {
200
+ const result = await model.generateContent(prompt);
201
+ const raw = result.response.text().replace(/```json\n?|```/g, '').trim();
202
+ return JSON.parse(raw);
203
+ } catch {
204
+ return this.fallbackNarrative(input.projectId, stats, input.safetyScore);
205
+ }
206
+ }
207
+
208
+ private fallbackNarrative(projectId: string, stats: ReturnType<typeof this.computeStats>, score: number) {
209
+ const mood = score >= 90 ? 'great' : score >= 70 ? 'good' : 'concerning';
210
+ return {
211
+ main: `This was a ${mood} month for ${projectId}. Your app handled ${stats.totalEvents.toLocaleString()} events. Our defenses blocked ${stats.attacksBlocked} attacks before they reached your users. ${stats.uniqueAttackers > 0 ? `${stats.uniqueAttackers} unique attackers gave it a shot — and failed.` : 'No significant threats were detected.'}`,
212
+ heroMoment: stats.attacksBlocked > 0
213
+ ? `The biggest save: intercepting a suspicious pattern from ${stats.topAttackOrigin} that could have impacted your users.`
214
+ : `Your app ran clean all month. Zero suspicious patterns detected.`,
215
+ threatPersonality: stats.uniqueAttackers > 0
216
+ ? `Your visitors included ${stats.uniqueAttackers} automated bots likely scanning for easy targets found via internet-wide port scans. They're not targeting you specifically — they're fishing.`
217
+ : `No meaningful threats appeared this month. Either your app is well-hidden or your defenses are scaring them off.`,
218
+ recommendation: score < 90
219
+ ? `Enable two-factor authentication on your admin accounts — it's the single highest-impact step you can take right now.`
220
+ : `You're in great shape! Consider enabling the Collective Defense Network to share threat signals with other TitanShieldAI users.`,
221
+ };
222
+ }
223
+
224
+ private computeGrade(score: number, blocked: number): BattleReport['grade'] {
225
+ if (score >= 95 && blocked === 0) return 'A+';
226
+ if (score >= 90) return 'A';
227
+ if (score >= 80) return 'B+';
228
+ if (score >= 70) return 'B';
229
+ if (score >= 60) return 'C';
230
+ if (score >= 40) return 'D';
231
+ return 'F';
232
+ }
233
+
234
+ private computeBadge(grade: string, blocked: number): string {
235
+ if (grade === 'A+') return '🏰 FORTRESS';
236
+ if (grade === 'A' && blocked > 10) return '⚔️ DEFENDER';
237
+ if (grade === 'A') return '🛡️ GUARDIAN';
238
+ if (grade.startsWith('B')) return '🔒 PROTECTOR';
239
+ if (grade === 'C') return '👮 WATCHER';
240
+ return '🌱 ROOKIE';
241
+ }
242
+
243
+ private buildShareText(projectId: string, period: string, stats: ReturnType<typeof this.computeStats>, grade: string, badge: string): string {
244
+ return `🛡️ My ${period} Security Battle Report — ${badge} ${grade}
245
+
246
+ My app "${projectId}" this month:
247
+ ✅ ${stats.totalEvents.toLocaleString()} total events handled
248
+ 🚫 ${stats.attacksBlocked} attacks blocked
249
+ 🔒 0 successful breaches
250
+ 👥 ${stats.uniqueAttackers} attackers tried... and failed
251
+
252
+ Powered by @TitanShieldAI — the only security tool that gives you a monthly battle story
253
+ Try it free: titanshield.ai`;
254
+ }
255
+
256
+ private buildHtmlCard(
257
+ projectId: string, period: string, stats: ReturnType<typeof this.computeStats>,
258
+ narrative: Awaited<ReturnType<typeof this.generateNarrative>>,
259
+ grade: string, badge: string, score: number
260
+ ): string {
261
+ const scoreColor = score >= 90 ? '#22c55e' : score >= 70 ? '#eab308' : '#ef4444';
262
+ return `<!DOCTYPE html>
263
+ <html><head><meta charset="UTF-8">
264
+ <style>
265
+ body{font-family:Inter,sans-serif;background:#020617;color:#e2e8f0;padding:0;margin:0}
266
+ .card{max-width:600px;margin:0 auto;background:linear-gradient(135deg,#0a0f1a,#1e0a3c);border:2px solid rgba(124,58,237,.4);border-radius:24px;overflow:hidden}
267
+ .hdr{background:linear-gradient(135deg,rgba(124,58,237,.3),rgba(6,182,212,.2));padding:28px 32px;display:flex;justify-content:space-between;align-items:flex-start}
268
+ .title{font-size:22px;font-weight:900;background:linear-gradient(135deg,#c4b5fd,#67e8f9);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
269
+ .badge{background:rgba(124,58,237,.3);border:1px solid rgba(124,58,237,.5);padding:6px 14px;border-radius:99px;font-size:14px;font-weight:800;color:#c4b5fd}
270
+ .body{padding:24px 32px}
271
+ .score{font-size:72px;font-weight:900;color:${scoreColor};line-height:1;margin-bottom:4px}
272
+ .stats{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin:20px 0}
273
+ .stat{background:rgba(255,255,255,.05);border-radius:12px;padding:14px;text-align:center}
274
+ .sn{font-size:28px;font-weight:900;color:#c4b5fd}
275
+ .sl{font-size:11px;color:#64748b;margin-top:4px;font-weight:600}
276
+ .story{background:rgba(0,0,0,.3);border-left:3px solid #7c3aed;border-radius:8px;padding:16px;font-size:14px;color:#cbd5e1;line-height:1.7;margin:16px 0}
277
+ .grade{background:rgba(34,197,94,.1);border:1px solid rgba(34,197,94,.3);border-radius:12px;padding:12px 16px;font-size:13px;color:#86efac;font-weight:600}
278
+ .ftr{border-top:1px solid rgba(255,255,255,.1);padding:16px 32px;font-size:11px;color:#334155;text-align:center}
279
+ </style></head><body>
280
+ <div class="card">
281
+ <div class="hdr">
282
+ <div><div class="title">🛡️ Battle Report</div><div style="font-size:13px;color:#64748b;margin-top:4px">${period} · ${projectId}</div></div>
283
+ <div class="badge">${badge} ${grade}</div>
284
+ </div>
285
+ <div class="body">
286
+ <div class="score">${score}</div>
287
+ <div style="font-size:13px;color:#64748b;margin-bottom:16px">Safety Score / 100</div>
288
+ <div class="stats">
289
+ <div class="stat"><div class="sn">${stats.totalEvents.toLocaleString()}</div><div class="sl">Events Handled</div></div>
290
+ <div class="stat"><div class="sn" style="color:#ef4444">${stats.attacksBlocked}</div><div class="sl">Attacks Blocked</div></div>
291
+ <div class="stat"><div class="sn" style="color:#7c3aed">${stats.uniqueAttackers}</div><div class="sl">Attackers Repelled</div></div>
292
+ </div>
293
+ <div class="story">${narrative.main}</div>
294
+ ${narrative.heroMoment ? `<div class="grade">⚔️ Hero Moment: ${narrative.heroMoment}</div>` : ''}
295
+ <div style="margin-top:12px;font-size:13px;color:#64748b">💡 ${narrative.recommendation}</div>
296
+ </div>
297
+ <div class="ftr">Generated by TitanShieldAI · titanshield.ai · The world's most secure developer SDK</div>
298
+ </div></body></html>`;
299
+ }
300
+ }