fodda-mcp 1.3.2 → 1.30.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 (109) hide show
  1. package/LICENSE +31 -0
  2. package/README.md +192 -20
  3. package/dist/a2aHandler.d.ts +20 -0
  4. package/dist/a2aHandler.d.ts.map +1 -0
  5. package/dist/a2aHandler.js +247 -0
  6. package/dist/a2aHandler.js.map +1 -0
  7. package/dist/agents/fodda-researcher/index.d.ts +35 -0
  8. package/dist/agents/fodda-researcher/index.d.ts.map +1 -0
  9. package/dist/agents/fodda-researcher/index.js +62 -0
  10. package/dist/agents/fodda-researcher/index.js.map +1 -0
  11. package/dist/agents/fodda-researcher/skills.d.ts +16 -0
  12. package/dist/agents/fodda-researcher/skills.d.ts.map +1 -0
  13. package/dist/agents/fodda-researcher/skills.js +163 -0
  14. package/dist/agents/fodda-researcher/skills.js.map +1 -0
  15. package/dist/brandTemplate.d.ts +14 -0
  16. package/dist/brandTemplate.d.ts.map +1 -0
  17. package/dist/brandTemplate.js +742 -0
  18. package/dist/brandTemplate.js.map +1 -0
  19. package/dist/catalogCache.d.ts +177 -0
  20. package/dist/catalogCache.d.ts.map +1 -0
  21. package/dist/catalogCache.js +708 -0
  22. package/dist/catalogCache.js.map +1 -0
  23. package/dist/editorialFill.d.ts +44 -0
  24. package/dist/editorialFill.d.ts.map +1 -0
  25. package/dist/editorialFill.js +377 -0
  26. package/dist/editorialFill.js.map +1 -0
  27. package/dist/enrichment.d.ts +74 -0
  28. package/dist/enrichment.d.ts.map +1 -0
  29. package/dist/enrichment.js +211 -0
  30. package/dist/enrichment.js.map +1 -0
  31. package/dist/errorHandling.d.ts +52 -0
  32. package/dist/errorHandling.d.ts.map +1 -0
  33. package/dist/errorHandling.js +327 -0
  34. package/dist/errorHandling.js.map +1 -0
  35. package/dist/index.d.ts +0 -1
  36. package/dist/index.js +595 -410
  37. package/dist/index.js.map +1 -1
  38. package/dist/pricingCache.d.ts +99 -0
  39. package/dist/pricingCache.d.ts.map +1 -0
  40. package/dist/pricingCache.js +414 -0
  41. package/dist/pricingCache.js.map +1 -0
  42. package/dist/queryCache.d.ts +25 -0
  43. package/dist/queryCache.d.ts.map +1 -0
  44. package/dist/queryCache.js +194 -0
  45. package/dist/queryCache.js.map +1 -0
  46. package/dist/searchTemplate.d.ts +16 -0
  47. package/dist/searchTemplate.d.ts.map +1 -0
  48. package/dist/searchTemplate.js +318 -0
  49. package/dist/searchTemplate.js.map +1 -0
  50. package/dist/sessionTracker.d.ts +39 -0
  51. package/dist/sessionTracker.d.ts.map +1 -0
  52. package/dist/sessionTracker.js +187 -0
  53. package/dist/sessionTracker.js.map +1 -0
  54. package/dist/skillClient.d.ts +142 -0
  55. package/dist/skillClient.d.ts.map +1 -0
  56. package/dist/skillClient.js +256 -0
  57. package/dist/skillClient.js.map +1 -0
  58. package/dist/svgVisuals.d.ts +77 -0
  59. package/dist/svgVisuals.d.ts.map +1 -0
  60. package/dist/svgVisuals.js +323 -0
  61. package/dist/svgVisuals.js.map +1 -0
  62. package/dist/systemPrompt.d.ts +26 -0
  63. package/dist/systemPrompt.d.ts.map +1 -0
  64. package/dist/systemPrompt.js +546 -0
  65. package/dist/systemPrompt.js.map +1 -0
  66. package/dist/test-waverunner-response.d.ts +2 -0
  67. package/dist/test-waverunner-response.d.ts.map +1 -0
  68. package/dist/test-waverunner-response.js +20 -0
  69. package/dist/test-waverunner-response.js.map +1 -0
  70. package/dist/test_analyst.d.ts +2 -0
  71. package/dist/test_analyst.d.ts.map +1 -0
  72. package/dist/test_analyst.js +39 -0
  73. package/dist/test_analyst.js.map +1 -0
  74. package/dist/test_analyst_local.d.ts +2 -0
  75. package/dist/test_analyst_local.d.ts.map +1 -0
  76. package/dist/test_analyst_local.js +51 -0
  77. package/dist/test_analyst_local.js.map +1 -0
  78. package/dist/test_live_mcp.d.ts +2 -0
  79. package/dist/test_live_mcp.d.ts.map +1 -0
  80. package/dist/test_live_mcp.js +49 -0
  81. package/dist/test_live_mcp.js.map +1 -0
  82. package/dist/test_skills.d.ts +9 -0
  83. package/dist/test_skills.d.ts.map +1 -0
  84. package/dist/test_skills.js +267 -0
  85. package/dist/test_skills.js.map +1 -0
  86. package/dist/toolHandlers.d.ts +11 -0
  87. package/dist/toolHandlers.d.ts.map +1 -0
  88. package/dist/toolHandlers.js +2829 -0
  89. package/dist/toolHandlers.js.map +1 -0
  90. package/dist/tools.d.ts +80 -3
  91. package/dist/tools.d.ts.map +1 -1
  92. package/dist/tools.js +89 -186
  93. package/dist/tools.js.map +1 -1
  94. package/dist/trialTracker.d.ts +58 -0
  95. package/dist/trialTracker.d.ts.map +1 -0
  96. package/dist/trialTracker.js +179 -0
  97. package/dist/trialTracker.js.map +1 -0
  98. package/dist/types.d.ts +3 -0
  99. package/dist/types.d.ts.map +1 -1
  100. package/dist/verify_hmac.js +1 -1
  101. package/dist/verify_hmac.js.map +1 -1
  102. package/dist/verify_tools_endpoint.js +5 -5
  103. package/dist/verify_tools_endpoint.js.map +1 -1
  104. package/dist/widgetShell.d.ts +15 -0
  105. package/dist/widgetShell.d.ts.map +1 -0
  106. package/dist/widgetShell.js +151 -0
  107. package/dist/widgetShell.js.map +1 -0
  108. package/package.json +10 -6
  109. package/server.json +12 -17
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Query-Result Cache — LRU in-memory cache for Fodda API responses.
3
+ *
4
+ * Eliminates redundant API calls when the same query is repeated within
5
+ * the TTL window. Keyed by method + path + body hash so identical
6
+ * requests always hit the same cache entry.
7
+ *
8
+ * TTL tiers:
9
+ * - search results: 5 minutes (graph data updates infrequently)
10
+ * - supplemental data: 15 minutes (institutional sources change rarely)
11
+ * - evidence/neighbors: 30 minutes (static once ingested)
12
+ * - brand-intelligence: 5 minutes (aggregated, same as search)
13
+ */
14
+ import crypto from 'crypto';
15
+ // ---------------------------------------------------------------------------
16
+ // Configuration
17
+ // ---------------------------------------------------------------------------
18
+ const MAX_ENTRIES = 500;
19
+ /** TTL in milliseconds by path pattern. First match wins. */
20
+ const TTL_RULES = [
21
+ { pattern: /\/evidence$/, ttlMs: 30 * 60 * 1000, label: 'evidence' },
22
+ { pattern: /\/neighbors$/, ttlMs: 30 * 60 * 1000, label: 'neighbors' },
23
+ { pattern: /\/supplemental\//, ttlMs: 15 * 60 * 1000, label: 'supplemental' },
24
+ { pattern: /\/brand-intelligence\//, ttlMs: 5 * 60 * 1000, label: 'brand-intelligence' },
25
+ { pattern: /\/search$/, ttlMs: 5 * 60 * 1000, label: 'search' },
26
+ { pattern: /\/label-values$/, ttlMs: 30 * 60 * 1000, label: 'label-values' },
27
+ { pattern: /\/adjacent$/, ttlMs: 15 * 60 * 1000, label: 'adjacent' },
28
+ { pattern: /\/overview$/, ttlMs: 5 * 60 * 1000, label: 'overview' },
29
+ { pattern: /\/statistics\/search$/, ttlMs: 10 * 60 * 1000, label: 'statistics' },
30
+ { pattern: /\/insights\/search$/, ttlMs: 10 * 60 * 1000, label: 'insights' },
31
+ ];
32
+ /** Paths that should NEVER be cached (dynamic, auth-dependent, or side-effectful). */
33
+ const UNCACHEABLE_PATTERNS = [
34
+ /\/v1\/graphs$/, // list_graphs — already cached by catalogCache
35
+ /\/widget\//, // widget serving
36
+ /\/register$/, // OAuth
37
+ ];
38
+ // ---------------------------------------------------------------------------
39
+ // Cache store
40
+ // ---------------------------------------------------------------------------
41
+ const cache = new Map();
42
+ // Stats for monitoring
43
+ let stats = {
44
+ hits: 0,
45
+ misses: 0,
46
+ evictions: 0,
47
+ stores: 0,
48
+ };
49
+ // ---------------------------------------------------------------------------
50
+ // Key generation
51
+ // ---------------------------------------------------------------------------
52
+ /**
53
+ * Build a deterministic cache key from the request signature.
54
+ * Uses raw string for small payloads (< 200 chars) to avoid crypto overhead.
55
+ * Falls back to SHA-256 for large POST bodies where the raw key would be unwieldy.
56
+ */
57
+ function buildCacheKey(method, path, body) {
58
+ const bodyStr = body ? JSON.stringify(body) : '';
59
+ const raw = `${method}:${path}:${bodyStr}`;
60
+ // Skip SHA-256 for small keys — most GET requests and light POST bodies
61
+ if (raw.length < 200)
62
+ return raw;
63
+ return crypto.createHash('sha256').update(raw).digest('hex');
64
+ }
65
+ // ---------------------------------------------------------------------------
66
+ // TTL resolution
67
+ // ---------------------------------------------------------------------------
68
+ function resolveTTL(path) {
69
+ for (const rule of TTL_RULES) {
70
+ if (rule.pattern.test(path)) {
71
+ return { ttlMs: rule.ttlMs, label: rule.label };
72
+ }
73
+ }
74
+ return null; // No matching rule — don't cache
75
+ }
76
+ function isCacheable(path) {
77
+ if (UNCACHEABLE_PATTERNS.some(p => p.test(path)))
78
+ return false;
79
+ return resolveTTL(path) !== null;
80
+ }
81
+ // ---------------------------------------------------------------------------
82
+ // LRU eviction
83
+ // ---------------------------------------------------------------------------
84
+ /**
85
+ * Evict the least-recently-accessed entry when cache is full.
86
+ * Also prunes expired entries opportunistically.
87
+ */
88
+ function evictIfNeeded() {
89
+ const now = Date.now();
90
+ // First pass: prune expired entries
91
+ for (const [key, entry] of cache) {
92
+ if (now - entry.createdAt > entry.ttlMs) {
93
+ cache.delete(key);
94
+ stats.evictions++;
95
+ }
96
+ }
97
+ // If still over limit, evict LRU
98
+ while (cache.size >= MAX_ENTRIES) {
99
+ let oldestKey = null;
100
+ let oldestAccess = Infinity;
101
+ for (const [key, entry] of cache) {
102
+ if (entry.lastAccessedAt < oldestAccess) {
103
+ oldestAccess = entry.lastAccessedAt;
104
+ oldestKey = key;
105
+ }
106
+ }
107
+ if (oldestKey) {
108
+ cache.delete(oldestKey);
109
+ stats.evictions++;
110
+ }
111
+ else {
112
+ break;
113
+ }
114
+ }
115
+ }
116
+ // ---------------------------------------------------------------------------
117
+ // Public API
118
+ // ---------------------------------------------------------------------------
119
+ /**
120
+ * Look up a cached response. Returns the cached data or null if miss/expired.
121
+ */
122
+ export function cacheGet(method, path, body) {
123
+ if (!isCacheable(path))
124
+ return null;
125
+ const key = buildCacheKey(method, path, body);
126
+ const entry = cache.get(key);
127
+ if (!entry) {
128
+ stats.misses++;
129
+ return null;
130
+ }
131
+ // Check TTL
132
+ if (Date.now() - entry.createdAt > entry.ttlMs) {
133
+ cache.delete(key);
134
+ stats.misses++;
135
+ return null;
136
+ }
137
+ // Cache hit
138
+ entry.hitCount++;
139
+ entry.lastAccessedAt = Date.now();
140
+ stats.hits++;
141
+ console.error(`[queryCache] HIT (${entry.label}) ${method} ${path} — hit #${entry.hitCount}, age ${Math.round((Date.now() - entry.createdAt) / 1000)}s`);
142
+ return entry.data;
143
+ }
144
+ /**
145
+ * Store a response in the cache.
146
+ */
147
+ export function cacheSet(method, path, body, data) {
148
+ if (!isCacheable(path))
149
+ return;
150
+ const ttlInfo = resolveTTL(path);
151
+ if (!ttlInfo)
152
+ return;
153
+ evictIfNeeded();
154
+ const key = buildCacheKey(method, path, body);
155
+ const now = Date.now();
156
+ cache.set(key, {
157
+ data,
158
+ createdAt: now,
159
+ ttlMs: ttlInfo.ttlMs,
160
+ label: ttlInfo.label,
161
+ hitCount: 0,
162
+ lastAccessedAt: now,
163
+ });
164
+ stats.stores++;
165
+ console.error(`[queryCache] STORE (${ttlInfo.label}) ${method} ${path} — TTL ${Math.round(ttlInfo.ttlMs / 1000)}s, cache size: ${cache.size}`);
166
+ }
167
+ /**
168
+ * Get cache statistics for monitoring/logging.
169
+ */
170
+ export function getCacheStats() {
171
+ const entriesByType = {};
172
+ for (const entry of cache.values()) {
173
+ entriesByType[entry.label] = (entriesByType[entry.label] || 0) + 1;
174
+ }
175
+ const total = stats.hits + stats.misses;
176
+ return {
177
+ size: cache.size,
178
+ hits: stats.hits,
179
+ misses: stats.misses,
180
+ evictions: stats.evictions,
181
+ stores: stats.stores,
182
+ hitRate: total > 0 ? `${Math.round((stats.hits / total) * 100)}%` : '0%',
183
+ entriesByType,
184
+ };
185
+ }
186
+ /**
187
+ * Clear all cache entries. Useful for testing or manual reset.
188
+ */
189
+ export function clearCache() {
190
+ cache.clear();
191
+ stats = { hits: 0, misses: 0, evictions: 0, stores: 0 };
192
+ console.error('[queryCache] Cache cleared');
193
+ }
194
+ //# sourceMappingURL=queryCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryCache.js","sourceRoot":"","sources":["../src/queryCache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Search Results Widget Template
3
+ *
4
+ * Server-side rendering for search_graph responses.
5
+ * Tabs: Trends, Companies, Market, Analysis
6
+ * Returns near-complete HTML with editorial slots for Claude.
7
+ */
8
+ export declare function renderSearchWidget(data: any, query: string, graphName?: string, supplemental?: {
9
+ google_trends?: any;
10
+ census_retail?: any;
11
+ }): Promise<{
12
+ widget_html: string;
13
+ editorial_instruction: string;
14
+ open_slots: string[];
15
+ }>;
16
+ //# sourceMappingURL=searchTemplate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"searchTemplate.d.ts","sourceRoot":"","sources":["../src/searchTemplate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA0NH,wBAAsB,kBAAkB,CACpC,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,GAAG,CAAC;IAAC,aAAa,CAAC,EAAE,GAAG,CAAA;CAAE,GAC5D,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,qBAAqB,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAiJvF"}
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Search Results Widget Template
3
+ *
4
+ * Server-side rendering for search_graph responses.
5
+ * Tabs: Trends, Companies, Market, Analysis
6
+ * Returns near-complete HTML with editorial slots for Claude.
7
+ */
8
+ import { wrapWidget, esc } from './widgetShell.js';
9
+ import { fillSearchInsight, fillAnalysis, replaceSlots } from './editorialFill.js';
10
+ function computeStage(t) {
11
+ const now = Date.now();
12
+ const signal = t.signal_score || 0;
13
+ const evCount = t.evidence_count || t.evidenceCount || 0;
14
+ const firstMs = t.firstSeen ? new Date(t.firstSeen).getTime() : now;
15
+ const lastMs = t.lastSeen ? new Date(t.lastSeen).getTime() : now;
16
+ const daysSinceLast = (now - lastMs) / 864e5;
17
+ const ageDays = (now - firstMs) / 864e5;
18
+ if (signal >= 70 && evCount >= 12 && daysSinceLast < 90)
19
+ return 'established';
20
+ if (signal >= 70 && daysSinceLast >= 90)
21
+ return 'plateauing';
22
+ if (signal < 45 && ageDays > 120)
23
+ return 'plateauing';
24
+ if (signal >= 45 || evCount >= 8)
25
+ return 'building';
26
+ return 'emerging';
27
+ }
28
+ function normalizeBrands(raw) {
29
+ if (!raw)
30
+ return [];
31
+ if (Array.isArray(raw))
32
+ return raw.slice(0, 4);
33
+ return raw.split('|').map(b => b.trim()).filter(Boolean).slice(0, 4);
34
+ }
35
+ // ---------------------------------------------------------------------------
36
+ // Build Google Trends section
37
+ // ---------------------------------------------------------------------------
38
+ function buildGoogleTrendsHtml(gt, query) {
39
+ if (!gt)
40
+ return '';
41
+ const snap = gt?.snapshot || gt;
42
+ const interest = snap?.interest_over_time;
43
+ if (!interest?.timeline_data?.length)
44
+ return '';
45
+ const points = interest.timeline_data;
46
+ const latest = points[points.length - 1];
47
+ const latestVal = latest?.values?.[0]?.extracted_value ?? latest?.value ?? 0;
48
+ const peak = Math.max(...points.map((p) => p?.values?.[0]?.extracted_value ?? p?.value ?? 0));
49
+ const peakPoint = points.find((p) => (p?.values?.[0]?.extracted_value ?? p?.value ?? 0) === peak);
50
+ const peakDate = peakPoint?.date || '';
51
+ const direction = latestVal > (points[Math.max(0, points.length - 4)]?.values?.[0]?.extracted_value ?? 0) ? '↑ rising' : '↓ declining';
52
+ // Build mini sparkline
53
+ const vals = points.map((p) => p?.values?.[0]?.extracted_value ?? p?.value ?? 0);
54
+ const max = Math.max(...vals, 1);
55
+ const svgW = 200;
56
+ const svgH = 32;
57
+ const pathPoints = vals.map((v, i) => `${(i / (vals.length - 1)) * svgW},${svgH - (v / max) * svgH}`).join(' ');
58
+ return `
59
+ <div class="sl2">Search interest — "${esc(query)}"</div>
60
+ <div style="display:flex;align-items:center;gap:16px;margin-bottom:8px;">
61
+ <svg viewBox="0 0 ${svgW} ${svgH}" width="${svgW}" height="${svgH}" style="flex-shrink:0;">
62
+ <polyline points="${pathPoints}" fill="none" stroke="var(--p)" stroke-width="1.5"/>
63
+ </svg>
64
+ <div style="font-size:11px;color:var(--color-text-secondary);line-height:1.5;">
65
+ <div>Now <strong>${latestVal}</strong> · Peak ${peak} (${esc(peakDate)})</div>
66
+ <div>${direction}</div>
67
+ </div>
68
+ </div>
69
+ <p class="fnote">Relative interest (0–100). Source: Google Trends.</p>`;
70
+ }
71
+ // ---------------------------------------------------------------------------
72
+ // Build Census Retail section
73
+ // ---------------------------------------------------------------------------
74
+ function buildCensusHtml(census) {
75
+ if (!census)
76
+ return '';
77
+ const snap = census?.snapshot || census;
78
+ const total = snap?.total_retail;
79
+ const subs = snap?.subcategories || [];
80
+ if (!total && !subs.length)
81
+ return '';
82
+ const fmtB = (v) => `$${(v / 1e9).toFixed(1)}B`;
83
+ const fmtPct = (v) => `${v >= 0 ? '+' : ''}${v.toFixed(1)}%`;
84
+ let html = `<div class="sl2">US retail sales — Census Bureau</div><div class="sg">`;
85
+ if (total) {
86
+ html += `<div class="sk"><div class="skl">Total US retail</div><div class="skv">${fmtB(total.value || 0)}</div><div class="sks">${fmtPct(total.mom_change || 0)} MoM</div></div>`;
87
+ }
88
+ // Show first subcategory
89
+ if (subs.length > 0) {
90
+ const sub = subs[0];
91
+ html += `<div class="sk"><div class="skl">${esc(sub.name || '')}</div><div class="skv">${fmtB(sub.value || 0)}</div><div class="sks">${fmtPct(sub.mom_change || 0)} MoM</div></div>`;
92
+ }
93
+ html += `</div>`;
94
+ return html;
95
+ }
96
+ // ---------------------------------------------------------------------------
97
+ // Build Companies section (brand frequency bars)
98
+ // ---------------------------------------------------------------------------
99
+ function buildCompaniesHtml(rows) {
100
+ const brandCounts = {};
101
+ rows.forEach(r => {
102
+ const brands = normalizeBrands(r.brandNames || r.Brand);
103
+ brands.forEach(b => {
104
+ brandCounts[b] = (brandCounts[b] || 0) + 1;
105
+ });
106
+ });
107
+ const sorted = Object.entries(brandCounts)
108
+ .sort((a, b) => b[1] - a[1])
109
+ .slice(0, 20);
110
+ if (sorted.length === 0)
111
+ return '<p class="fnote">No brand data available for these trends.</p>';
112
+ return `<div class="co-grid">${sorted.map(([brand, count]) => {
113
+ const query = `Tell me about ${brand.replace(/'/g, "\\'")}'s innovation strategy across the knowledge graphs`;
114
+ return `<button class="co-pill" onclick="sendPrompt('${query}')">${esc(brand)}<span class="co-count">${count}</span></button>`;
115
+ }).join('\n')}</div>`;
116
+ }
117
+ // ---------------------------------------------------------------------------
118
+ // CSS
119
+ // ---------------------------------------------------------------------------
120
+ const SEARCH_CSS = `
121
+ /* Grid */
122
+ .tgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:10px;}
123
+ .tc2{background:var(--color-background-primary);border:1px solid var(--color-border-tertiary);border-radius:4px;
124
+ padding:1rem 1.125rem;display:flex;flex-direction:column;gap:6px;transition:opacity .25s,transform .25s;}
125
+
126
+ .tc2-top{display:flex;justify-content:space-between;align-items:flex-start;gap:6px;}
127
+ .tc2-eyebrow{font-family:var(--font-mono);font-size:10px;font-weight:700;letter-spacing:0.18em;text-transform:uppercase;color:var(--color-text-secondary);margin-bottom:2px;}
128
+ .tc2-eyebrow .sep{color:var(--color-border-tertiary);}
129
+ .signal{display:inline-flex;align-items:baseline;gap:6px;flex-shrink:0;}
130
+ .signal-num{font-family:var(--font-display);font-style:italic;font-size:18px;font-weight:400;color:var(--color-text-primary);}
131
+ .signal-meta{font-family:var(--font-mono);font-size:9px;letter-spacing:0.16em;text-transform:uppercase;color:var(--color-text-secondary);}
132
+ .signal.high .signal-num{color:var(--color-text-success);}
133
+ .signal.mid .signal-num{color:var(--color-text-warning);}
134
+ .signal.low .signal-num{color:var(--color-text-secondary);}
135
+ .tc2-name{font-size:13px;font-weight:500;line-height:1.35;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;}
136
+ .tc2-desc{font-size:11px;color:var(--color-text-secondary);line-height:1.5;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;}
137
+ .bchips{display:flex;flex-wrap:wrap;gap:3px;}
138
+ .bchip{font-size:11px;background:var(--color-background-secondary);color:var(--color-text-secondary);border-radius:4px;padding:1px 6px;}
139
+ .tc2-foot{display:flex;justify-content:space-between;align-items:center;font-size:10px;color:var(--color-text-tertiary);margin-top:2px;gap:6px;}
140
+ .tc2-graph{font-size:10px;padding:2px 8px;border-radius:20px;background:var(--pl);color:var(--pl-on);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:70%;}
141
+ .tc2-exp{font-size:11px;border:1px solid var(--color-border-tertiary);border-radius:4px;padding:3px 10px;
142
+ cursor:pointer;background:transparent;color:var(--color-text-secondary);margin-top:auto;width:fit-content;
143
+ transition:all .15s;font-family:var(--font-mono);}
144
+ .tc2-exp:hover{background:var(--pl);border-color:var(--pm);color:var(--pl-on);}
145
+ .search-insight{font-size:14px;font-family:var(--font-display);font-style:italic;font-weight:400;line-height:1.65;padding:14px 16px;
146
+ background:var(--pl);border:1px solid var(--p);border-left-width:3px;border-radius:4px;margin-bottom:1.25rem;color:var(--color-text-primary);}
147
+
148
+ /* Company pills */
149
+ .co-grid{display:flex;flex-wrap:wrap;gap:6px;}
150
+ .co-pill{display:inline-flex;align-items:center;gap:5px;font-size:12px;padding:5px 12px;border-radius:20px;
151
+ border:.5px solid var(--color-border-tertiary);background:none;color:var(--color-text-primary);
152
+ cursor:pointer;font-family:var(--font-mono);transition:all .15s;}
153
+ .co-pill:hover{background:var(--pl);border-color:var(--pm);color:var(--pl-on);}
154
+ .co-count{font-size:10px;background:var(--color-background-secondary);color:var(--color-text-tertiary);
155
+ border-radius:10px;padding:1px 5px;}
156
+
157
+ /* Market stats */
158
+ .sg{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin-bottom:12px;}
159
+ .sk{background:var(--color-background-secondary);border-radius:8px;padding:10px 12px;}
160
+ .skl{font-size:10px;color:var(--color-text-secondary);margin-bottom:2px;}
161
+ .skv{font-family:var(--font-display);font-style:italic;font-size:22px;font-weight:400;}
162
+ .sks{font-size:10px;color:var(--color-text-secondary);}
163
+ .sl2{font-size:13px;font-weight:500;margin:1rem 0 8px;}.sl2:first-child{margin-top:0;}
164
+
165
+ /* Analysis */
166
+ .analysis-content{font-size:13px;line-height:1.65;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;}
167
+ .analysis-content p{margin-bottom:10px;}
168
+ .analysis-content strong{font-weight:600;}
169
+
170
+ /* Dark mode */
171
+ @media(prefers-color-scheme:dark){
172
+ .bchip{background:rgba(255,255,255,0.08);}
173
+ }
174
+ `;
175
+ // ---------------------------------------------------------------------------
176
+ // Stage definitions
177
+ // ---------------------------------------------------------------------------
178
+ const STAGES = [
179
+ { id: 'all', label: 'All', color: '#444441', desc: 'Every signal in the result set' },
180
+ { id: 'emerging', label: 'Emerging', color: '#A07CD4', desc: 'New signals — thin evidence, high uncertainty' },
181
+ { id: 'building', label: 'Building', color: '#2E6BE5', desc: 'Gaining momentum — evidence accumulating' },
182
+ { id: 'established', label: 'Established', color: '#1D9E75', desc: 'Well-documented — high signal, broad coverage' },
183
+ { id: 'plateauing', label: 'Plateauing', color: '#888780', desc: 'Slowing — signal strong but evidence aging' },
184
+ ];
185
+ // ---------------------------------------------------------------------------
186
+ // Main render function
187
+ // ---------------------------------------------------------------------------
188
+ export async function renderSearchWidget(data, query, graphName, supplemental) {
189
+ const rows = data?.rows || data?.results || [];
190
+ // Only render widget for 3+ results; 1-2 results → Claude uses raw JSON
191
+ if (rows.length < 3) {
192
+ return {
193
+ widget_html: '',
194
+ editorial_instruction: '',
195
+ open_slots: [],
196
+ };
197
+ }
198
+ // Compute stages
199
+ const enriched = rows.map(r => ({
200
+ ...r,
201
+ _stage: r.trendLifecycle || computeStage(r),
202
+ _signal: r.signal_score || 0,
203
+ _name: r.trendName || r.name || r.label || '',
204
+ _desc: (r.trendDescription || r.description || '').slice(0, 140),
205
+ _brands: normalizeBrands(r.brandNames || r.Brand),
206
+ _graphName: r.graphName || graphName || '',
207
+ _evCount: r.evidence_count || r.evidenceCount || 0,
208
+ _firstSeen: r.firstSeen || '',
209
+ _lastSeen: r.lastSeen || '',
210
+ }));
211
+ enriched.sort((a, b) => b._signal - a._signal);
212
+ // Collect source names
213
+ const sourceSet = new Set();
214
+ enriched.forEach(t => { if (t._graphName)
215
+ sourceSet.add(t._graphName); });
216
+ if (supplemental?.google_trends)
217
+ sourceSet.add('Google Trends');
218
+ if (supplemental?.census_retail)
219
+ sourceSet.add('Census Bureau');
220
+ const sources = [...sourceSet];
221
+ // Build Market tab content
222
+ const censusHtml = buildCensusHtml(supplemental?.census_retail);
223
+ const gtHtml = buildGoogleTrendsHtml(supplemental?.google_trends, query);
224
+ const marketContent = (censusHtml || gtHtml)
225
+ ? `${censusHtml}${gtHtml}`
226
+ : '<p class="fnote">No market data available for this query.</p>';
227
+ // Build Companies tab content
228
+ const companiesHtml = buildCompaniesHtml(rows);
229
+ const signalCountHtml = `<p class="scr-count">${enriched.length} signals across ${sources.filter(s => s !== 'Google Trends' && s !== 'Census Bureau').join(', ')}</p>`;
230
+ // Build Trends grid server-side
231
+ const smap = Object.fromEntries(STAGES.map(s => [s.id, s]));
232
+ const trendsGridHtml = enriched.map(t => {
233
+ const stageObj = smap[t._stage] || smap['emerging'];
234
+ const sc = t._signal >= 80 ? 'high' : t._signal >= 50 ? 'mid' : 'low';
235
+ const queryStr = `Tell me about ${t._name} from the ${t._graphName}`.replace(/'/g, "\\'");
236
+ return `<div class="tc2">
237
+ <div class="tc2-eyebrow">${esc(t._graphName)} <span class="sep">·</span> ${(stageObj?.label || 'Emerging').toUpperCase()}</div>
238
+ <div class="tc2-top"><p class="tc2-name">${esc(t._name)}</p>
239
+ <div class="signal ${sc}"><span class="signal-num">${t._signal}</span><span class="signal-meta">signal</span></div></div>
240
+ <p class="tc2-desc">${esc(t._desc)}</p>
241
+ <div class="bchips">${t._brands.map((b) => `<span class="bchip">${esc(b)}</span>`).join('')}</div>
242
+ <div class="tc2-foot"><span class="tc2-graph">${esc(t._graphName)}</span><span>${t.relevance ? t.relevance + '% match' : t._evCount + ' ev.'}</span></div>
243
+ <button class="tc2-exp" onclick="sendPrompt('${queryStr}')">Explore ↗</button></div>`;
244
+ }).join('');
245
+ // Build content HTML linearly without tabs
246
+ const contentHtml = `
247
+ <div class="search-insight">{{SEARCH_INSIGHT}}</div>
248
+
249
+ <div class="sec">Trends</div>
250
+ ${signalCountHtml}
251
+ <div class="tgrid" id="tgrid">${trendsGridHtml}</div>
252
+
253
+ <div class="sec">Companies</div>
254
+ <div class="sl2">Brands appearing across these trends</div>
255
+ <p class="fnote">Frequency of brand mentions across ${enriched.length} trends. Higher count = wider strategic footprint.</p>
256
+ ${companiesHtml}
257
+
258
+ <div class="sec">Market</div>
259
+ ${marketContent}
260
+
261
+ <div class="sec">Analysis</div>
262
+ <div class="analysis-content">{{ANALYSIS_HTML}}</div>
263
+ `;
264
+ const subtitle = `Search: ${query}`;
265
+ let widget_html = wrapWidget(subtitle, contentHtml, sources, SEARCH_CSS);
266
+ // Count unique brands for editorial context
267
+ const uniqueBrands = new Set();
268
+ enriched.forEach(t => t._brands.forEach(b => uniqueBrands.add(b)));
269
+ // ── Tier 1 + Tier 2: Run both Gemini fills in parallel ──
270
+ const trendSummaries = enriched.map(t => ({
271
+ name: t._name, signal: t._signal, stage: t._stage,
272
+ graphName: t._graphName, brands: t._brands, desc: t._desc,
273
+ }));
274
+ const suppSummary = supplemental?.google_trends ? 'Google Trends data available for query.' : '';
275
+ const [insightResult, analysisResult] = await Promise.allSettled([
276
+ fillSearchInsight(trendSummaries, query),
277
+ fillAnalysis(query, trendSummaries, suppSummary),
278
+ ]);
279
+ const insightText = insightResult.status === 'fulfilled' ? insightResult.value : '';
280
+ const analysisHtml = analysisResult.status === 'fulfilled' ? analysisResult.value : '';
281
+ if (analysisResult.status === 'rejected') {
282
+ console.error(`[searchTemplate] fillAnalysis failed:`, analysisResult.reason);
283
+ }
284
+ // Replace slots
285
+ const fills = {};
286
+ if (insightText)
287
+ fills['SEARCH_INSIGHT'] = insightText;
288
+ if (analysisHtml)
289
+ fills['ANALYSIS_HTML'] = analysisHtml;
290
+ widget_html = replaceSlots(widget_html, fills);
291
+ const slotsRemaining = [];
292
+ if (!insightText)
293
+ slotsRemaining.push('SEARCH_INSIGHT');
294
+ if (!analysisHtml)
295
+ slotsRemaining.push('ANALYSIS_HTML');
296
+ const editorial_instruction = slotsRemaining.length === 0
297
+ ? `── SEARCH WIDGET: READY ──
298
+ The widget is fully populated. Call show_widget with the widget_html as-is. Do NOT modify the HTML.`
299
+ : `── SEARCH WIDGET: EDITORIAL SLOTS ──
300
+ The widget_html has ${slotsRemaining.length} unfilled slot(s): ${slotsRemaining.join(', ')}.
301
+ Fill them and call show_widget.
302
+
303
+ CONTEXT:
304
+ - Query: "${query}"
305
+ - ${enriched.length} trends across ${sources.join(', ')}
306
+ - Top signal: "${enriched[0]?._name}" (score ${enriched[0]?._signal})
307
+ - ${uniqueBrands.size} unique brands
308
+
309
+ CRITICAL: ALL output must go INSIDE the widget slots. Do NOT redesign or restyle.`;
310
+ // Final cleanup: strip any remaining unfilled {{...}} placeholders
311
+ widget_html = widget_html.replace(/\{\{[A-Z_0-9]+\}\}/g, '');
312
+ return {
313
+ widget_html,
314
+ editorial_instruction,
315
+ open_slots: slotsRemaining,
316
+ };
317
+ }
318
+ //# sourceMappingURL=searchTemplate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"searchTemplate.js","sourceRoot":"","sources":["../src/searchTemplate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Session Tracker — in-memory search history and frustration detection.
3
+ *
4
+ * Tracks tool call patterns within a single MCP session to detect
5
+ * structural frustration signals (repeated queries, NO_MATCH streaks,
6
+ * graph bouncing). Hints are injected into response text for Claude
7
+ * to act on — the user never sees them directly.
8
+ *
9
+ * When aggregate frustration is high (3+ patterns triggered in one session),
10
+ * an alert is posted to #fodda-sales on Slack for the sales bot to enrich.
11
+ *
12
+ * Resets per MCP connection (stateless across sessions).
13
+ */
14
+ export interface SessionSearch {
15
+ query: string;
16
+ graphId: string;
17
+ resultCount: number;
18
+ timestamp: number;
19
+ }
20
+ export type FrustrationPattern = 'LOW_YIELD' | 'NO_MATCH' | 'GRAPH_BOUNCING';
21
+ export interface FrustrationDetails {
22
+ pattern: FrustrationPattern;
23
+ graphsTried: string[];
24
+ recentQueries: string[];
25
+ score: number;
26
+ }
27
+ /**
28
+ * Post a message to #fodda-sales via the Slack Bot Token.
29
+ * Fire-and-forget — errors are logged but never thrown.
30
+ */
31
+ export declare function postToSlack(text: string): Promise<void>;
32
+ export declare function createSessionTracker(): {
33
+ trackSearch: (query: string, graphId: string, resultCount: number) => void;
34
+ detectFrustration: () => string | null;
35
+ getRecentSearches: (n?: number) => SessionSearch[];
36
+ getFrustrationDetails: () => FrustrationDetails | null;
37
+ postFrustrationToSlack: (userIdentifier: string) => void;
38
+ };
39
+ //# sourceMappingURL=sessionTracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionTracker.d.ts","sourceRoot":"","sources":["../src/sessionTracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAE7E,MAAM,WAAW,kBAAkB;IAC/B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACjB;AAQD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB7D;AAMD,wBAAgB,oBAAoB;yBAOJ,MAAM,WAAW,MAAM,eAAe,MAAM,KAAG,IAAI;6BAsEjD,MAAM,GAAG,IAAI;4BA1Db,MAAM,KAAO,aAAa,EAAE;iCAQxB,kBAAkB,GAAG,IAAI;6CAuGX,MAAM,KAAG,IAAI;EAuBhE"}