@crowdlisten/harness 1.0.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/AGENTS.md +167 -0
  2. package/LICENSE +21 -0
  3. package/README.md +153 -0
  4. package/dist/agent-proxy.d.ts +24 -0
  5. package/dist/agent-proxy.js +140 -0
  6. package/dist/agent-tools.d.ts +736 -0
  7. package/dist/agent-tools.js +409 -0
  8. package/dist/context/api.d.ts +5 -0
  9. package/dist/context/api.js +164 -0
  10. package/dist/context/cli.d.ts +19 -0
  11. package/dist/context/cli.js +108 -0
  12. package/dist/context/extractor.d.ts +12 -0
  13. package/dist/context/extractor.js +43 -0
  14. package/dist/context/index.d.ts +12 -0
  15. package/dist/context/index.js +11 -0
  16. package/dist/context/matcher.d.ts +39 -0
  17. package/dist/context/matcher.js +246 -0
  18. package/dist/context/parser.d.ts +28 -0
  19. package/dist/context/parser.js +157 -0
  20. package/dist/context/pipeline.d.ts +26 -0
  21. package/dist/context/pipeline.js +56 -0
  22. package/dist/context/prompts.d.ts +6 -0
  23. package/dist/context/prompts.js +60 -0
  24. package/dist/context/providers.d.ts +6 -0
  25. package/dist/context/providers.js +106 -0
  26. package/dist/context/redactor.d.ts +10 -0
  27. package/dist/context/redactor.js +68 -0
  28. package/dist/context/server.d.ts +5 -0
  29. package/dist/context/server.js +134 -0
  30. package/dist/context/store.d.ts +12 -0
  31. package/dist/context/store.js +82 -0
  32. package/dist/context/types.d.ts +79 -0
  33. package/dist/context/types.js +4 -0
  34. package/dist/context/user-state.d.ts +40 -0
  35. package/dist/context/user-state.js +144 -0
  36. package/dist/index.d.ts +14 -0
  37. package/dist/index.js +385 -0
  38. package/dist/insights/browser/BrowserPool.d.ts +87 -0
  39. package/dist/insights/browser/BrowserPool.js +266 -0
  40. package/dist/insights/browser/RequestInterceptor.d.ts +46 -0
  41. package/dist/insights/browser/RequestInterceptor.js +115 -0
  42. package/dist/insights/cli.d.ts +8 -0
  43. package/dist/insights/cli.js +206 -0
  44. package/dist/insights/core/base/BaseAdapter.d.ts +37 -0
  45. package/dist/insights/core/base/BaseAdapter.js +123 -0
  46. package/dist/insights/core/health/HealthMonitor.d.ts +75 -0
  47. package/dist/insights/core/health/HealthMonitor.js +171 -0
  48. package/dist/insights/core/interfaces/SocialMediaPlatform.d.ts +125 -0
  49. package/dist/insights/core/interfaces/SocialMediaPlatform.js +42 -0
  50. package/dist/insights/core/utils/DataNormalizer.d.ts +53 -0
  51. package/dist/insights/core/utils/DataNormalizer.js +349 -0
  52. package/dist/insights/core/utils/InstagramUrlUtils.d.ts +11 -0
  53. package/dist/insights/core/utils/InstagramUrlUtils.js +60 -0
  54. package/dist/insights/core/utils/TikTokUrlUtils.d.ts +10 -0
  55. package/dist/insights/core/utils/TikTokUrlUtils.js +57 -0
  56. package/dist/insights/handlers.d.ts +157 -0
  57. package/dist/insights/handlers.js +246 -0
  58. package/dist/insights/index.d.ts +437 -0
  59. package/dist/insights/index.js +426 -0
  60. package/dist/insights/platforms/instagram/InstagramAdapter.d.ts +34 -0
  61. package/dist/insights/platforms/instagram/InstagramAdapter.js +342 -0
  62. package/dist/insights/platforms/moltbook/MoltbookAdapter.d.ts +31 -0
  63. package/dist/insights/platforms/moltbook/MoltbookAdapter.js +227 -0
  64. package/dist/insights/platforms/reddit/RedditAdapter.d.ts +21 -0
  65. package/dist/insights/platforms/reddit/RedditAdapter.js +212 -0
  66. package/dist/insights/platforms/tiktok/TikTokAdapter.d.ts +34 -0
  67. package/dist/insights/platforms/tiktok/TikTokAdapter.js +269 -0
  68. package/dist/insights/platforms/twitter/TwitterAdapter.d.ts +23 -0
  69. package/dist/insights/platforms/twitter/TwitterAdapter.js +211 -0
  70. package/dist/insights/platforms/xiaohongshu/XiaohongshuAdapter.d.ts +35 -0
  71. package/dist/insights/platforms/xiaohongshu/XiaohongshuAdapter.js +258 -0
  72. package/dist/insights/platforms/youtube/YouTubeAdapter.d.ts +22 -0
  73. package/dist/insights/platforms/youtube/YouTubeAdapter.js +254 -0
  74. package/dist/insights/service-config.d.ts +7 -0
  75. package/dist/insights/service-config.js +60 -0
  76. package/dist/insights/services/UnifiedSocialMediaService.d.ts +94 -0
  77. package/dist/insights/services/UnifiedSocialMediaService.js +259 -0
  78. package/dist/insights/vision/VisionExtractor.d.ts +46 -0
  79. package/dist/insights/vision/VisionExtractor.js +236 -0
  80. package/dist/learnings.d.ts +50 -0
  81. package/dist/learnings.js +130 -0
  82. package/dist/openapi.d.ts +29 -0
  83. package/dist/openapi.js +169 -0
  84. package/dist/server-factory.d.ts +20 -0
  85. package/dist/server-factory.js +41 -0
  86. package/dist/suggestions.d.ts +16 -0
  87. package/dist/suggestions.js +72 -0
  88. package/dist/telemetry.d.ts +44 -0
  89. package/dist/telemetry.js +93 -0
  90. package/dist/tools/registry.d.ts +65 -0
  91. package/dist/tools/registry.js +256 -0
  92. package/dist/tools.d.ts +2433 -0
  93. package/dist/tools.js +2294 -0
  94. package/dist/transport/http.d.ts +15 -0
  95. package/dist/transport/http.js +154 -0
  96. package/package.json +76 -0
  97. package/skills/catalog.json +272 -0
  98. package/skills/community-catalog.json +4202 -0
  99. package/skills/competitive-analysis/SKILL.md +174 -0
  100. package/skills/content-creator/SKILL.md +256 -0
  101. package/skills/content-strategy/SKILL.md +222 -0
  102. package/skills/data-storytelling/SKILL.md +248 -0
  103. package/skills/heuristic-evaluation/SKILL.md +201 -0
  104. package/skills/market-research-reports/SKILL.md +184 -0
  105. package/skills/user-stories/SKILL.md +178 -0
  106. package/skills/ux-researcher/SKILL.md +239 -0
  107. package/web-dist/assets/index-B1b25lNd.css +1 -0
  108. package/web-dist/assets/index-CDWHwHbl.js +64 -0
  109. package/web-dist/index.html +16 -0
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Streamable HTTP Transport for CrowdListen Harness
4
+ *
5
+ * Exposes the full MCP tool surface over HTTP. Stateless mode —
6
+ * each request extracts auth from Authorization header.
7
+ *
8
+ * Endpoints:
9
+ * POST /mcp — MCP tool calls (StreamableHTTPServerTransport)
10
+ * GET /mcp — SSE connection for streaming
11
+ * DELETE /mcp — Session cleanup
12
+ * GET /health — Health check
13
+ * GET /openapi.json — Auto-generated OpenAPI spec
14
+ */
15
+ export declare function startHttpTransport(port?: number): Promise<void>;
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Streamable HTTP Transport for CrowdListen Harness
4
+ *
5
+ * Exposes the full MCP tool surface over HTTP. Stateless mode —
6
+ * each request extracts auth from Authorization header.
7
+ *
8
+ * Endpoints:
9
+ * POST /mcp — MCP tool calls (StreamableHTTPServerTransport)
10
+ * GET /mcp — SSE connection for streaming
11
+ * DELETE /mcp — Session cleanup
12
+ * GET /health — Health check
13
+ * GET /openapi.json — Auto-generated OpenAPI spec
14
+ */
15
+ import * as http from "http";
16
+ import * as crypto from "crypto";
17
+ import { createClient } from "@supabase/supabase-js";
18
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
19
+ import { createMcpServer, SERVER_VERSION } from "../server-factory.js";
20
+ import { generateOpenApiSpec } from "../openapi.js";
21
+ import { TOOLS } from "../tools.js";
22
+ const CROWDLISTEN_SUPABASE_URL = process.env.CROWDLISTEN_URL || "https://fnvlxtzonwybshtvrzit.supabase.co";
23
+ const CROWDLISTEN_ANON_KEY = process.env.CROWDLISTEN_ANON_KEY ||
24
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZudmx4dHpvbnd5YnNodHZyeml0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTY4NjExMjksImV4cCI6MjA3MjQzNzEyOX0.KAoEVMAVxqANcHBrjT5Et_9xiMZGP7LzdVSoSDLxpaA";
25
+ // ─── Auth Extraction ───────────────────────────────────────────────────────
26
+ async function authenticateRequest(req
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ ) {
29
+ const authHeader = req.headers.authorization;
30
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
31
+ throw new Error("Authorization header required: Bearer <token>");
32
+ }
33
+ const token = authHeader.slice(7);
34
+ const supabase = createClient(CROWDLISTEN_SUPABASE_URL, CROWDLISTEN_ANON_KEY, {
35
+ auth: { autoRefreshToken: false, persistSession: false },
36
+ });
37
+ // Try as JWT first
38
+ const { data, error } = await supabase.auth.getUser(token);
39
+ if (!error && data.user) {
40
+ // Set session so RLS works — refresh_token must be non-empty
41
+ // for Supabase to properly set the auth context
42
+ await supabase.auth.setSession({
43
+ access_token: token,
44
+ refresh_token: token, // reuse access_token; won't refresh but RLS works
45
+ });
46
+ return { supabase, userId: data.user.id };
47
+ }
48
+ // Could be an API key — look up in api_keys table
49
+ const anonSb = createClient(CROWDLISTEN_SUPABASE_URL, CROWDLISTEN_ANON_KEY);
50
+ const { data: keyRow } = await anonSb
51
+ .from("api_keys")
52
+ .select("user_id")
53
+ .eq("key_hash", hashApiKey(token))
54
+ .eq("revoked", false)
55
+ .single();
56
+ if (keyRow) {
57
+ return { supabase: anonSb, userId: keyRow.user_id };
58
+ }
59
+ throw new Error("Invalid token or API key");
60
+ }
61
+ function hashApiKey(key) {
62
+ return crypto.createHash("sha256").update(key).digest("hex");
63
+ }
64
+ // ─── CORS Headers ──────────────────────────────────────────────────────────
65
+ function setCorsHeaders(res) {
66
+ res.setHeader("Access-Control-Allow-Origin", "*");
67
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
68
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept");
69
+ res.setHeader("Access-Control-Max-Age", "86400");
70
+ }
71
+ // ─── HTTP Server ───────────────────────────────────────────────────────────
72
+ export async function startHttpTransport(port = 3848) {
73
+ // Cache the OpenAPI spec
74
+ let cachedSpec = null;
75
+ const httpServer = http.createServer(async (req, res) => {
76
+ setCorsHeaders(res);
77
+ // Handle CORS preflight
78
+ if (req.method === "OPTIONS") {
79
+ res.writeHead(204);
80
+ res.end();
81
+ return;
82
+ }
83
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
84
+ // ── Health Check ─────────────────────────────────────────
85
+ if (url.pathname === "/health" && req.method === "GET") {
86
+ res.writeHead(200, { "Content-Type": "application/json" });
87
+ res.end(JSON.stringify({
88
+ status: "ok",
89
+ version: SERVER_VERSION,
90
+ tools: TOOLS.length,
91
+ transport: "streamable-http",
92
+ }));
93
+ return;
94
+ }
95
+ // ── OpenAPI Spec ─────────────────────────────────────────
96
+ if (url.pathname === "/openapi.json" && req.method === "GET") {
97
+ if (!cachedSpec) {
98
+ cachedSpec = JSON.stringify(generateOpenApiSpec(), null, 2);
99
+ }
100
+ res.writeHead(200, { "Content-Type": "application/json" });
101
+ res.end(cachedSpec);
102
+ return;
103
+ }
104
+ // ── MCP Endpoints ────────────────────────────────────────
105
+ if (url.pathname === "/mcp") {
106
+ try {
107
+ const { supabase, userId } = await authenticateRequest(req);
108
+ const server = createMcpServer({ supabase, userId });
109
+ const transport = new StreamableHTTPServerTransport({
110
+ sessionIdGenerator: undefined, // Stateless mode
111
+ });
112
+ await server.connect(transport);
113
+ await transport.handleRequest(req, res);
114
+ // Clean up after request completes
115
+ await server.close();
116
+ }
117
+ catch (err) {
118
+ const message = err instanceof Error ? err.message : String(err);
119
+ const status = message.includes("Authorization") ? 401 : 500;
120
+ res.writeHead(status, { "Content-Type": "application/json" });
121
+ res.end(JSON.stringify({ error: message }));
122
+ }
123
+ return;
124
+ }
125
+ // ── 404 ──────────────────────────────────────────────────
126
+ res.writeHead(404, { "Content-Type": "application/json" });
127
+ res.end(JSON.stringify({
128
+ error: "Not found",
129
+ endpoints: ["/mcp", "/health", "/openapi.json"],
130
+ }));
131
+ });
132
+ httpServer.listen(port, () => {
133
+ console.error(`
134
+ CrowdListen Harness HTTP server running
135
+
136
+ MCP: http://localhost:${port}/mcp
137
+ Health: http://localhost:${port}/health
138
+ OpenAPI: http://localhost:${port}/openapi.json
139
+
140
+ Tools: ${TOOLS.length} across planning, analysis, content, generation, LLM, and agent network
141
+
142
+ Connect from any MCP client:
143
+ { "url": "http://localhost:${port}/mcp" }
144
+ `);
145
+ });
146
+ // Graceful shutdown
147
+ process.on("SIGINT", () => {
148
+ console.error("\nShutting down...");
149
+ httpServer.close(() => process.exit(0));
150
+ });
151
+ process.on("SIGTERM", () => {
152
+ httpServer.close(() => process.exit(0));
153
+ });
154
+ }
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@crowdlisten/harness",
3
+ "version": "1.0.0",
4
+ "description": "Crowd context for AI agents — analyzed crowd intelligence from social platforms, with planning, skill packs, and semantic recall",
5
+ "type": "module",
6
+ "bin": {
7
+ "crowdlisten-harness": "dist/index.js",
8
+ "crowdlisten-serve": "dist/transport/http.js",
9
+ "crowdlisten": "dist/insights/cli.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "build:web": "cd src/context/web && npx vite build --outDir ../../../web-dist",
14
+ "build:catalog": "node scripts/build-catalog.js",
15
+ "build:all": "npm run build:catalog && npm run build && npm run build:web",
16
+ "dev": "tsx src/index.ts",
17
+ "start": "node dist/index.js",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest",
20
+ "test:coverage": "vitest run --coverage",
21
+ "test:e2e": "vitest run --config vitest.e2e.config.ts"
22
+ },
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.12.1",
25
+ "@supabase/supabase-js": "^2.49.4",
26
+ "@the-convocation/twitter-scraper": "^0.22.1",
27
+ "axios": "^1.13.1",
28
+ "commander": "^14.0.3",
29
+ "dotenv": "^17.3.1",
30
+ "jszip": "^3.10.1",
31
+ "node-fetch": "^3.3.2",
32
+ "pdf-parse": "^1.1.1"
33
+ },
34
+ "devDependencies": {
35
+ "tsx": "^4.19.0",
36
+ "typescript": "^5.7.0",
37
+ "@types/node": "^22.0.0",
38
+ "playwright": "^1.58.2",
39
+ "vitest": "^3.0.0",
40
+ "@vitest/coverage-v8": "^3.0.0",
41
+ "react": "^19.0.0",
42
+ "react-dom": "^19.0.0",
43
+ "@types/react": "^19.0.0",
44
+ "@types/react-dom": "^19.0.0",
45
+ "vite": "^6.0.0",
46
+ "@vitejs/plugin-react": "^4.3.0",
47
+ "tailwindcss": "^4.0.0",
48
+ "@tailwindcss/vite": "^4.0.0"
49
+ },
50
+ "files": [
51
+ "dist",
52
+ "web-dist",
53
+ "skills",
54
+ "README.md",
55
+ "AGENTS.md",
56
+ "LICENSE"
57
+ ],
58
+ "keywords": [
59
+ "mcp",
60
+ "planning",
61
+ "delegation",
62
+ "coding-agent",
63
+ "crowdlisten",
64
+ "knowledge-base",
65
+ "context-extraction",
66
+ "social-media",
67
+ "crowd-intelligence",
68
+ "skill-packs"
69
+ ],
70
+ "license": "MIT",
71
+ "repository": {
72
+ "type": "git",
73
+ "url": "https://github.com/Crowdlisten/crowdlisten_harness.git"
74
+ },
75
+ "homepage": "https://crowdlisten.com"
76
+ }
@@ -0,0 +1,272 @@
1
+ [
2
+ {
3
+ "id": "competitive-analysis",
4
+ "name": "crowdlisten:competitive-analysis",
5
+ "description": "Competitive intelligence through audience perception analysis. Use when comparing brands, products, or competitors in social conversations. Requires CROWDLISTEN_API_KEY.",
6
+ "keywords": [
7
+ "competitive",
8
+ "intelligence",
9
+ "audience",
10
+ "perception",
11
+ "analysis",
12
+ "comparing",
13
+ "brands",
14
+ "products",
15
+ "competitors",
16
+ "social",
17
+ "conversations",
18
+ "crowdlisten",
19
+ "positioning",
20
+ "strategy",
21
+ "market",
22
+ "entry",
23
+ "landscape",
24
+ "assessment",
25
+ "tracking",
26
+ "competitor",
27
+ "changes",
28
+ "time",
29
+ "identifying",
30
+ "underserved",
31
+ "segments",
32
+ "feature",
33
+ "identification",
34
+ "preparing",
35
+ "fundraising",
36
+ "board",
37
+ "meetings",
38
+ "strategic",
39
+ "reviews"
40
+ ]
41
+ },
42
+ {
43
+ "id": "content-creator",
44
+ "name": "crowdlisten:content-creator",
45
+ "description": "Generate SEO-optimized marketing content with brand voice analysis. Blog posts, social media, email campaigns. Use when creating any branded content.",
46
+ "keywords": [
47
+ "generate",
48
+ "seo-optimized",
49
+ "marketing",
50
+ "content",
51
+ "brand",
52
+ "voice",
53
+ "analysis",
54
+ "blog",
55
+ "posts",
56
+ "social",
57
+ "media",
58
+ "email",
59
+ "campaigns",
60
+ "creating",
61
+ "branded",
62
+ "creator"
63
+ ]
64
+ },
65
+ {
66
+ "id": "content-strategy",
67
+ "name": "crowdlisten:content-strategy",
68
+ "description": "Data-driven content strategy grounded in audience demand. Topic demand analysis, content gaps, platform optimization, voice matching, campaign tracking. Requires CROWDLISTEN_API_KEY.",
69
+ "keywords": [
70
+ "data-driven",
71
+ "content",
72
+ "strategy",
73
+ "grounded",
74
+ "audience",
75
+ "demand",
76
+ "topic",
77
+ "analysis",
78
+ "gaps",
79
+ "platform",
80
+ "optimization",
81
+ "voice",
82
+ "matching",
83
+ "campaign",
84
+ "tracking",
85
+ "crowdlisten",
86
+ "calendar",
87
+ "planning",
88
+ "audience-validated",
89
+ "topics",
90
+ "finding",
91
+ "competitors",
92
+ "haven",
93
+ "covered",
94
+ "optimizing",
95
+ "format",
96
+ "timing",
97
+ "adapting",
98
+ "brand",
99
+ "match",
100
+ "communication",
101
+ "style",
102
+ "measuring",
103
+ "resonance",
104
+ "adjusting",
105
+ "product",
106
+ "launches",
107
+ "performance",
108
+ "monitoring",
109
+ "real-time",
110
+ "sentiment",
111
+ "during",
112
+ "launch",
113
+ "windows",
114
+ "crisis",
115
+ "early",
116
+ "warning",
117
+ "message",
118
+ "scoring"
119
+ ]
120
+ },
121
+ {
122
+ "id": "data-storytelling",
123
+ "name": "crowdlisten:data-storytelling",
124
+ "description": "Transform data and analysis into compelling narratives. Problem-Solution, Trend, Comparison, Hero's Journey frameworks. Use when presenting insights to stakeholders.",
125
+ "keywords": [
126
+ "transform",
127
+ "data",
128
+ "analysis",
129
+ "compelling",
130
+ "narratives",
131
+ "problem-solution",
132
+ "trend",
133
+ "comparison",
134
+ "hero",
135
+ "journey",
136
+ "frameworks",
137
+ "presenting",
138
+ "insights",
139
+ "stakeholders",
140
+ "storytelling"
141
+ ]
142
+ },
143
+ {
144
+ "id": "heuristic-evaluation",
145
+ "name": "crowdlisten:heuristic-evaluation",
146
+ "description": "Customer feedback analysis from social conversations. Journey friction detection, pain point mapping, persona generation, churn risk signals. Requires CROWDLISTEN_API_KEY.",
147
+ "keywords": [
148
+ "customer",
149
+ "feedback",
150
+ "analysis",
151
+ "social",
152
+ "conversations",
153
+ "journey",
154
+ "friction",
155
+ "detection",
156
+ "pain",
157
+ "point",
158
+ "mapping",
159
+ "persona",
160
+ "generation",
161
+ "churn",
162
+ "risk",
163
+ "signals",
164
+ "crowdlisten",
165
+ "experience",
166
+ "improvement",
167
+ "optimization",
168
+ "clustering",
169
+ "usability",
170
+ "signal",
171
+ "organic",
172
+ "discussions",
173
+ "support",
174
+ "strategy",
175
+ "ticket",
176
+ "deflection",
177
+ "health",
178
+ "monitoring",
179
+ "prevention",
180
+ "identifying",
181
+ "profiling",
182
+ "target",
183
+ "audience",
184
+ "segments",
185
+ "heuristic",
186
+ "evaluation"
187
+ ]
188
+ },
189
+ {
190
+ "id": "market-research-reports",
191
+ "name": "crowdlisten:market-research",
192
+ "description": "Generate comprehensive market research reports with Porter's Five Forces, PESTLE, SWOT, TAM/SAM/SOM, BCG Matrix. Use for market analysis, competitive landscape, industry trends.",
193
+ "keywords": [
194
+ "generate",
195
+ "comprehensive",
196
+ "market",
197
+ "research",
198
+ "reports",
199
+ "porter",
200
+ "five",
201
+ "forces",
202
+ "pestle",
203
+ "swot",
204
+ "matrix",
205
+ "analysis",
206
+ "competitive",
207
+ "landscape",
208
+ "industry",
209
+ "trends"
210
+ ]
211
+ },
212
+ {
213
+ "id": "user-stories",
214
+ "name": "crowdlisten:user-stories",
215
+ "description": "Turn social listening data into product decisions. JTBD extraction, feature demand scoring, persona generation, user story generation from real audience data. Requires CROWDLISTEN_API_KEY.",
216
+ "keywords": [
217
+ "turn",
218
+ "social",
219
+ "listening",
220
+ "data",
221
+ "product",
222
+ "decisions",
223
+ "jtbd",
224
+ "extraction",
225
+ "feature",
226
+ "demand",
227
+ "scoring",
228
+ "persona",
229
+ "generation",
230
+ "user",
231
+ "story",
232
+ "real",
233
+ "audience",
234
+ "crowdlisten",
235
+ "planning",
236
+ "roadmap",
237
+ "prioritization",
238
+ "discussions",
239
+ "request",
240
+ "analysis",
241
+ "early-stage",
242
+ "idea",
243
+ "validation",
244
+ "against",
245
+ "stories"
246
+ ]
247
+ },
248
+ {
249
+ "id": "ux-researcher",
250
+ "name": "crowdlisten:ux-research",
251
+ "description": "UX research synthesis — user personas, journey maps, heuristic evaluation (Nielsen's 10), design recommendations. Use when analyzing user behavior or designing experiences.",
252
+ "keywords": [
253
+ "research",
254
+ "synthesis",
255
+ "user",
256
+ "personas",
257
+ "journey",
258
+ "maps",
259
+ "heuristic",
260
+ "evaluation",
261
+ "nielsen",
262
+ "design",
263
+ "recommendations",
264
+ "analyzing",
265
+ "behavior",
266
+ "designing",
267
+ "experiences",
268
+ "researcher",
269
+ "designer"
270
+ ]
271
+ }
272
+ ]