@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,349 @@
1
+ /**
2
+ * Data normalization utilities for converting platform-specific data
3
+ * into standardized formats
4
+ */
5
+ /**
6
+ * Normalize user data from different platforms
7
+ */
8
+ export class DataNormalizer {
9
+ /**
10
+ * Normalize a post from any platform to standard format
11
+ */
12
+ static normalizePost(rawData, platform, baseUrl = '') {
13
+ switch (platform) {
14
+ case 'tiktok':
15
+ return this.normalizeTikTokPost(rawData);
16
+ case 'twitter':
17
+ return this.normalizeTwitterPost(rawData);
18
+ case 'reddit':
19
+ return this.normalizeRedditPost(rawData);
20
+ case 'instagram':
21
+ return this.normalizeInstagramPost(rawData);
22
+ case 'youtube':
23
+ return this.normalizeYouTubePost(rawData);
24
+ default:
25
+ throw new Error(`Unsupported platform: ${platform}`);
26
+ }
27
+ }
28
+ /**
29
+ * Normalize user data from any platform
30
+ */
31
+ static normalizeUser(rawData, platform) {
32
+ switch (platform) {
33
+ case 'tiktok':
34
+ return this.normalizeTikTokUser(rawData);
35
+ case 'twitter':
36
+ return this.normalizeTwitterUser(rawData);
37
+ case 'reddit':
38
+ return this.normalizeRedditUser(rawData);
39
+ case 'instagram':
40
+ return this.normalizeInstagramUser(rawData);
41
+ case 'youtube':
42
+ return this.normalizeYouTubeUser(rawData);
43
+ default:
44
+ throw new Error(`Unsupported platform: ${platform}`);
45
+ }
46
+ }
47
+ /**
48
+ * Normalize comment data from any platform
49
+ */
50
+ static normalizeComment(rawData, platform) {
51
+ switch (platform) {
52
+ case 'tiktok':
53
+ return this.normalizeTikTokComment(rawData);
54
+ case 'twitter':
55
+ return this.normalizeTwitterComment(rawData);
56
+ case 'reddit':
57
+ return this.normalizeRedditComment(rawData);
58
+ case 'instagram':
59
+ return this.normalizeInstagramComment(rawData);
60
+ case 'youtube':
61
+ return this.normalizeYouTubeComment(rawData);
62
+ default:
63
+ throw new Error(`Unsupported platform: ${platform}`);
64
+ }
65
+ }
66
+ // TikTok normalization methods
67
+ static normalizeTikTokPost(data) {
68
+ return {
69
+ id: data.id || data.aweme_id || '',
70
+ platform: 'tiktok',
71
+ author: this.normalizeTikTokUser(data.author || {}),
72
+ content: data.desc || data.description || '',
73
+ mediaUrl: data.video?.play_addr?.url_list?.[0] || '',
74
+ engagement: {
75
+ likes: data.stats?.diggCount || data.statistics?.digg_count || 0,
76
+ comments: data.stats?.commentCount || data.statistics?.comment_count || 0,
77
+ shares: data.stats?.shareCount || data.statistics?.share_count || 0,
78
+ views: data.stats?.playCount || data.statistics?.play_count || 0
79
+ },
80
+ timestamp: new Date((data.createTime || data.create_time || Date.now() / 1000) * 1000),
81
+ url: data.webVideoUrl || `https://www.tiktok.com/@${data.author?.uniqueId}/video/${data.id}`,
82
+ hashtags: this.extractHashtags(data.desc || data.description || '')
83
+ };
84
+ }
85
+ static normalizeTikTokUser(data) {
86
+ return {
87
+ id: data.id || data.uid || '',
88
+ username: data.uniqueId || data.unique_id || '',
89
+ displayName: data.nickname || data.nick_name || '',
90
+ followerCount: data.followerCount || data.follower_count || 0,
91
+ verified: data.verified || false,
92
+ profileImageUrl: data.avatarLarger || data.avatar_larger || '',
93
+ bio: data.signature || ''
94
+ };
95
+ }
96
+ static normalizeTikTokComment(data) {
97
+ return {
98
+ id: data.cid || data.comment_id || '',
99
+ author: this.normalizeTikTokUser(data.user || {}),
100
+ text: data.text || '',
101
+ timestamp: new Date((data.create_time || Date.now() / 1000) * 1000),
102
+ likes: data.digg_count || 0,
103
+ replies: data.reply_comment?.map((reply) => this.normalizeTikTokComment(reply)) || []
104
+ };
105
+ }
106
+ // Twitter normalization methods (agent-twitter-client Tweet shape)
107
+ static normalizeTwitterPost(data) {
108
+ return {
109
+ id: data.id || '',
110
+ platform: 'twitter',
111
+ author: this.normalizeTwitterUser(data),
112
+ content: data.text || '',
113
+ mediaUrl: data.photos?.[0]?.url || data.videos?.[0]?.preview || '',
114
+ engagement: {
115
+ likes: data.likes || 0,
116
+ comments: data.replies || 0,
117
+ shares: data.retweets || 0,
118
+ views: data.views || 0
119
+ },
120
+ timestamp: data.timeParsed ? new Date(data.timeParsed) :
121
+ data.timestamp ? new Date(data.timestamp * 1000) : new Date(),
122
+ url: data.permanentUrl || `https://twitter.com/${data.username || 'user'}/status/${data.id}`,
123
+ hashtags: data.hashtags || this.extractHashtags(data.text || '')
124
+ };
125
+ }
126
+ static normalizeTwitterUser(data) {
127
+ return {
128
+ id: data.userId || data.id || '',
129
+ username: data.username || '',
130
+ displayName: data.name || '',
131
+ followerCount: 0,
132
+ verified: false,
133
+ profileImageUrl: '',
134
+ bio: ''
135
+ };
136
+ }
137
+ static normalizeTwitterComment(data) {
138
+ // Twitter replies normalized as comments
139
+ return {
140
+ id: data.id || '',
141
+ author: this.normalizeTwitterUser(data),
142
+ text: data.text || '',
143
+ timestamp: data.timeParsed ? new Date(data.timeParsed) :
144
+ data.timestamp ? new Date(data.timestamp * 1000) : new Date(),
145
+ likes: data.likes || 0,
146
+ replies: []
147
+ };
148
+ }
149
+ // Reddit normalization methods
150
+ static normalizeRedditPost(data) {
151
+ return {
152
+ id: data.id || '',
153
+ platform: 'reddit',
154
+ author: this.normalizeRedditUser(data.author_display_name || data.author || ''),
155
+ content: data.selftext || data.title || '',
156
+ mediaUrl: data.url_overridden_by_dest || data.url || '',
157
+ engagement: {
158
+ likes: data.score || data.ups || 0,
159
+ comments: data.num_comments || data.comment_count || 0,
160
+ shares: 0, // Reddit doesn't have shares
161
+ views: 0 // Reddit doesn't track views publicly
162
+ },
163
+ timestamp: new Date((data.created_utc || data.created || Date.now() / 1000) * 1000),
164
+ url: `https://reddit.com${data.permalink}`,
165
+ hashtags: []
166
+ };
167
+ }
168
+ static normalizeRedditUser(data) {
169
+ if (typeof data === 'string') {
170
+ return {
171
+ id: data,
172
+ username: data,
173
+ displayName: data
174
+ };
175
+ }
176
+ return {
177
+ id: data.id || data.name || '',
178
+ username: data.name || data.display_name || '',
179
+ displayName: data.display_name || data.name || '',
180
+ followerCount: 0, // Reddit doesn't expose follower counts
181
+ verified: data.is_gold || false,
182
+ profileImageUrl: data.icon_img || '',
183
+ bio: data.public_description || ''
184
+ };
185
+ }
186
+ static normalizeRedditComment(data) {
187
+ // Reddit nests replies as a Listing: replies.data.children[]
188
+ // replies can also be an empty string when there are no replies
189
+ const replyChildren = data.replies?.data?.children || [];
190
+ const replies = replyChildren
191
+ .filter((child) => child.kind === 't1' && child.data)
192
+ .map((child) => this.normalizeRedditComment(child.data));
193
+ return {
194
+ id: data.id || '',
195
+ author: this.normalizeRedditUser(data.author_display_name || data.author || ''),
196
+ text: data.body || '',
197
+ timestamp: new Date((data.created_utc || Date.now() / 1000) * 1000),
198
+ likes: data.score || data.ups || 0,
199
+ replies
200
+ };
201
+ }
202
+ // Instagram normalization methods
203
+ static normalizeInstagramPost(data) {
204
+ return {
205
+ id: data.id || data.pk || '',
206
+ platform: 'instagram',
207
+ author: this.normalizeInstagramUser(data.user || data.owner || {}),
208
+ content: data.caption?.text || data.edge_media_to_caption?.edges?.[0]?.node?.text || '',
209
+ mediaUrl: data.image_versions2?.candidates?.[0]?.url || data.display_url || '',
210
+ engagement: {
211
+ likes: data.like_count || data.edge_media_preview_like?.count || 0,
212
+ comments: data.comment_count || data.edge_media_to_comment?.count || 0,
213
+ shares: 0, // Instagram doesn't expose shares
214
+ views: data.view_count || data.video_view_count || 0
215
+ },
216
+ timestamp: new Date((data.taken_at || data.taken_at_timestamp || Date.now() / 1000) * 1000),
217
+ url: `https://www.instagram.com/p/${data.code || data.shortcode}/`,
218
+ hashtags: this.extractHashtags(data.caption?.text || '')
219
+ };
220
+ }
221
+ static normalizeInstagramUser(data) {
222
+ return {
223
+ id: data.pk || data.id || '',
224
+ username: data.username || '',
225
+ displayName: data.full_name || data.username || '',
226
+ followerCount: data.follower_count || data.edge_followed_by?.count || 0,
227
+ verified: data.is_verified || false,
228
+ profileImageUrl: data.profile_pic_url || data.profile_pic_url_hd || '',
229
+ bio: data.biography || data.bio || ''
230
+ };
231
+ }
232
+ static normalizeInstagramComment(data) {
233
+ return {
234
+ id: data.pk || data.id || '',
235
+ author: this.normalizeInstagramUser(data.user || {}),
236
+ text: data.text || '',
237
+ timestamp: new Date((data.created_at || data.created_at_utc || Date.now() / 1000) * 1000),
238
+ likes: data.comment_like_count || 0,
239
+ replies: data.child_comment_count ? [] : [] // Instagram API complex for replies
240
+ };
241
+ }
242
+ // YouTube normalization methods
243
+ static normalizeYouTubePost(data) {
244
+ const snippet = data.snippet || {};
245
+ const stats = data.statistics || {};
246
+ const videoId = data.id?.videoId || data.id || '';
247
+ return {
248
+ id: videoId,
249
+ platform: 'youtube',
250
+ author: this.normalizeYouTubeUser(snippet),
251
+ content: snippet.title || '',
252
+ mediaUrl: snippet.thumbnails?.high?.url || snippet.thumbnails?.default?.url || '',
253
+ engagement: {
254
+ likes: parseInt(stats.likeCount || '0', 10),
255
+ comments: parseInt(stats.commentCount || '0', 10),
256
+ shares: 0,
257
+ views: parseInt(stats.viewCount || '0', 10)
258
+ },
259
+ timestamp: new Date(snippet.publishedAt || Date.now()),
260
+ url: `https://www.youtube.com/watch?v=${videoId}`,
261
+ hashtags: this.extractHashtags(snippet.title || '')
262
+ };
263
+ }
264
+ static normalizeYouTubeUser(data) {
265
+ return {
266
+ id: data.channelId || '',
267
+ username: data.channelTitle || '',
268
+ displayName: data.channelTitle || '',
269
+ followerCount: 0,
270
+ verified: false,
271
+ profileImageUrl: '',
272
+ bio: ''
273
+ };
274
+ }
275
+ static normalizeYouTubeComment(data) {
276
+ const topLevel = data.snippet?.topLevelComment?.snippet || {};
277
+ const replies = (data.replies?.comments || []).map((reply) => {
278
+ const rs = reply.snippet || {};
279
+ return {
280
+ id: reply.id || '',
281
+ author: {
282
+ id: rs.authorChannelId?.value || '',
283
+ username: rs.authorDisplayName || '',
284
+ displayName: rs.authorDisplayName || ''
285
+ },
286
+ text: rs.textDisplay || rs.textOriginal || '',
287
+ timestamp: new Date(rs.publishedAt || Date.now()),
288
+ likes: rs.likeCount || 0,
289
+ replies: []
290
+ };
291
+ });
292
+ return {
293
+ id: data.id || '',
294
+ author: {
295
+ id: topLevel.authorChannelId?.value || '',
296
+ username: topLevel.authorDisplayName || '',
297
+ displayName: topLevel.authorDisplayName || ''
298
+ },
299
+ text: topLevel.textDisplay || topLevel.textOriginal || '',
300
+ timestamp: new Date(topLevel.publishedAt || Date.now()),
301
+ likes: topLevel.likeCount || 0,
302
+ replies
303
+ };
304
+ }
305
+ /**
306
+ * Extract hashtags from text
307
+ */
308
+ static extractHashtags(text) {
309
+ const hashtagRegex = /#[\w\u00c0-\u024f\u1e00-\u1eff]+/gi;
310
+ const matches = text.match(hashtagRegex);
311
+ return matches ? matches.map(tag => tag.toLowerCase()) : [];
312
+ }
313
+ /**
314
+ * Calculate engagement rate
315
+ */
316
+ static calculateEngagementRate(likes, comments, shares = 0, followerCount) {
317
+ if (followerCount === 0)
318
+ return 0;
319
+ return ((likes + comments + shares) / followerCount) * 100;
320
+ }
321
+ /**
322
+ * Sanitize and validate text content
323
+ */
324
+ static sanitizeText(text) {
325
+ if (!text)
326
+ return '';
327
+ return text
328
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '') // Remove control characters
329
+ .trim()
330
+ .substring(0, 2000); // Limit length
331
+ }
332
+ /**
333
+ * Normalize timestamp from various formats
334
+ */
335
+ static normalizeTimestamp(timestamp) {
336
+ if (!timestamp)
337
+ return new Date();
338
+ if (timestamp instanceof Date)
339
+ return timestamp;
340
+ if (typeof timestamp === 'string') {
341
+ return new Date(timestamp);
342
+ }
343
+ if (typeof timestamp === 'number') {
344
+ // Handle both seconds and milliseconds
345
+ return new Date(timestamp < 1e12 ? timestamp * 1000 : timestamp);
346
+ }
347
+ return new Date();
348
+ }
349
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shared Instagram URL utilities for detection, redirect resolution, and shortcode extraction.
3
+ */
4
+ export declare class InstagramUrlUtils {
5
+ private static readonly INSTAGRAM_HOST_PATTERN;
6
+ static isInstagramUrl(input: string): boolean;
7
+ static isReelUrl(input: string): boolean;
8
+ static resolveUrl(input: string): Promise<string>;
9
+ static extractShortcode(input: string): string | null;
10
+ private static tryParseUrl;
11
+ }
@@ -0,0 +1,60 @@
1
+ import axios from 'axios';
2
+ /**
3
+ * Shared Instagram URL utilities for detection, redirect resolution, and shortcode extraction.
4
+ */
5
+ export class InstagramUrlUtils {
6
+ static INSTAGRAM_HOST_PATTERN = /(^|\.)instagram\.com$/i;
7
+ static isInstagramUrl(input) {
8
+ const parsed = this.tryParseUrl(input);
9
+ if (!parsed)
10
+ return false;
11
+ return this.INSTAGRAM_HOST_PATTERN.test(parsed.hostname);
12
+ }
13
+ static isReelUrl(input) {
14
+ const parsed = this.tryParseUrl(input);
15
+ if (!parsed)
16
+ return false;
17
+ return /\/(reel|reels)\//i.test(parsed.pathname);
18
+ }
19
+ static async resolveUrl(input) {
20
+ const parsed = this.tryParseUrl(input);
21
+ if (!parsed) {
22
+ return input;
23
+ }
24
+ try {
25
+ const response = await axios.get(parsed.toString(), {
26
+ maxRedirects: 5,
27
+ timeout: 10000,
28
+ validateStatus: (status) => status >= 200 && status < 400,
29
+ });
30
+ const finalUrl = response.request?.res?.responseUrl;
31
+ return typeof finalUrl === 'string' && finalUrl.length > 0 ? finalUrl : parsed.toString();
32
+ }
33
+ catch {
34
+ return parsed.toString();
35
+ }
36
+ }
37
+ static extractShortcode(input) {
38
+ const parsed = this.tryParseUrl(input);
39
+ if (!parsed) {
40
+ return null;
41
+ }
42
+ // Match /reel/{shortcode}/ or /p/{shortcode}/ or /reels/{shortcode}/
43
+ const match = parsed.pathname.match(/\/(reel|reels|p)\/([A-Za-z0-9_-]+)/);
44
+ if (match?.[2]) {
45
+ return match[2];
46
+ }
47
+ return null;
48
+ }
49
+ static tryParseUrl(input) {
50
+ if (!input || input.trim().length === 0) {
51
+ return null;
52
+ }
53
+ try {
54
+ return new URL(input.trim());
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared TikTok URL utilities for detection, redirect resolution, and video ID extraction.
3
+ */
4
+ export declare class TikTokUrlUtils {
5
+ private static readonly TIKTOK_HOST_PATTERN;
6
+ static isTikTokUrl(input: string): boolean;
7
+ static resolveUrl(input: string): Promise<string>;
8
+ static extractVideoId(input: string): string | null;
9
+ private static tryParseUrl;
10
+ }
@@ -0,0 +1,57 @@
1
+ import axios from 'axios';
2
+ /**
3
+ * Shared TikTok URL utilities for detection, redirect resolution, and video ID extraction.
4
+ */
5
+ export class TikTokUrlUtils {
6
+ static TIKTOK_HOST_PATTERN = /(^|\.)tiktok\.com$/i;
7
+ static isTikTokUrl(input) {
8
+ const parsed = this.tryParseUrl(input);
9
+ if (!parsed)
10
+ return false;
11
+ return this.TIKTOK_HOST_PATTERN.test(parsed.hostname);
12
+ }
13
+ static async resolveUrl(input) {
14
+ const parsed = this.tryParseUrl(input);
15
+ if (!parsed) {
16
+ return input;
17
+ }
18
+ try {
19
+ const response = await axios.get(parsed.toString(), {
20
+ maxRedirects: 5,
21
+ timeout: 10000,
22
+ validateStatus: (status) => status >= 200 && status < 400,
23
+ });
24
+ const finalUrl = response.request?.res?.responseUrl;
25
+ return typeof finalUrl === 'string' && finalUrl.length > 0 ? finalUrl : parsed.toString();
26
+ }
27
+ catch {
28
+ return parsed.toString();
29
+ }
30
+ }
31
+ static extractVideoId(input) {
32
+ const parsed = this.tryParseUrl(input);
33
+ if (!parsed) {
34
+ return null;
35
+ }
36
+ const pathMatch = parsed.pathname.match(/\/video\/(\d+)/);
37
+ if (pathMatch?.[1]) {
38
+ return pathMatch[1];
39
+ }
40
+ const itemId = parsed.searchParams.get('item_id') || parsed.searchParams.get('aweme_id');
41
+ if (itemId && /^\d+$/.test(itemId)) {
42
+ return itemId;
43
+ }
44
+ return null;
45
+ }
46
+ static tryParseUrl(input) {
47
+ if (!input || input.trim().length === 0) {
48
+ return null;
49
+ }
50
+ try {
51
+ return new URL(input.trim());
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * CrowdListen Shared Handlers
3
+ * Pure functions that return plain objects — used by CLI and MCP server.
4
+ *
5
+ * Retrieval handlers (free, local): search, comments, trending, user content, vision
6
+ * Analysis handlers (paid, API): analyze, cluster, enrich, deep_analyze, insights, research
7
+ */
8
+ import { UnifiedSocialMediaService } from './services/UnifiedSocialMediaService.js';
9
+ import { HealthMonitor } from './core/health/HealthMonitor.js';
10
+ export interface SearchArgs {
11
+ platform: string;
12
+ query: string;
13
+ limit?: number;
14
+ useVision?: boolean;
15
+ }
16
+ export interface CommentsArgs {
17
+ platform: string;
18
+ contentId: string;
19
+ limit?: number;
20
+ useVision?: boolean;
21
+ }
22
+ export interface AnalyzeArgs {
23
+ platform: string;
24
+ contentId: string;
25
+ analysisDepth?: 'surface' | 'standard' | 'deep' | 'comprehensive';
26
+ }
27
+ export interface ClusterArgs {
28
+ platform: string;
29
+ contentId: string;
30
+ clusterCount?: number;
31
+ includeExamples?: boolean;
32
+ weightByEngagement?: boolean;
33
+ }
34
+ export interface EnrichArgs {
35
+ platform: string;
36
+ contentId: string;
37
+ question?: string;
38
+ }
39
+ export interface TrendingArgs {
40
+ platform: string;
41
+ limit?: number;
42
+ }
43
+ export interface UserContentArgs {
44
+ platform: string;
45
+ userId: string;
46
+ limit?: number;
47
+ }
48
+ export interface ExtractUrlArgs {
49
+ url: string;
50
+ mode?: 'posts' | 'comments' | 'raw';
51
+ limit?: number;
52
+ }
53
+ export declare function extractWithVision(args: ExtractUrlArgs): Promise<{
54
+ raw?: string | undefined;
55
+ count?: number | undefined;
56
+ comments?: import("./core/interfaces/SocialMediaPlatform.js").Comment[] | undefined;
57
+ posts?: import("./core/interfaces/SocialMediaPlatform.js").Post[] | undefined;
58
+ url: string;
59
+ mode: "posts" | "comments" | "raw";
60
+ provider: string;
61
+ extractionMethod: string;
62
+ }>;
63
+ export declare function getTrendingContent(service: UnifiedSocialMediaService, args: TrendingArgs): Promise<{
64
+ platform: string;
65
+ count: number;
66
+ posts: import("./core/interfaces/SocialMediaPlatform.js").Post[];
67
+ }>;
68
+ export declare function getUserContent(service: UnifiedSocialMediaService, args: UserContentArgs): Promise<{
69
+ platform: string;
70
+ userId: string;
71
+ count: number;
72
+ posts: import("./core/interfaces/SocialMediaPlatform.js").Post[];
73
+ }>;
74
+ export declare function searchContent(service: UnifiedSocialMediaService, args: SearchArgs): Promise<{
75
+ raw?: string | undefined;
76
+ count?: number | undefined;
77
+ comments?: import("./core/interfaces/SocialMediaPlatform.js").Comment[] | undefined;
78
+ posts?: import("./core/interfaces/SocialMediaPlatform.js").Post[] | undefined;
79
+ url: string;
80
+ mode: "posts" | "comments" | "raw";
81
+ provider: string;
82
+ extractionMethod: string;
83
+ } | {
84
+ platform: string;
85
+ query: string;
86
+ count: number;
87
+ posts: import("./core/interfaces/SocialMediaPlatform.js").Post[];
88
+ }>;
89
+ export declare function getContentComments(service: UnifiedSocialMediaService, args: CommentsArgs): Promise<{
90
+ raw?: string | undefined;
91
+ count?: number | undefined;
92
+ comments?: import("./core/interfaces/SocialMediaPlatform.js").Comment[] | undefined;
93
+ posts?: import("./core/interfaces/SocialMediaPlatform.js").Post[] | undefined;
94
+ url: string;
95
+ mode: "posts" | "comments" | "raw";
96
+ provider: string;
97
+ extractionMethod: string;
98
+ } | {
99
+ platform: string;
100
+ contentId: string;
101
+ count: number;
102
+ comments: import("./core/interfaces/SocialMediaPlatform.js").Comment[];
103
+ }>;
104
+ export declare function getPlatformStatus(service: UnifiedSocialMediaService): {
105
+ availablePlatforms: {
106
+ tiktok?: any;
107
+ twitter?: any;
108
+ reddit?: any;
109
+ instagram?: any;
110
+ youtube?: any;
111
+ moltbook?: any;
112
+ xiaohongshu?: any;
113
+ };
114
+ totalPlatforms: number;
115
+ };
116
+ export declare function healthCheck(service: UnifiedSocialMediaService, monitor?: HealthMonitor): Promise<{
117
+ overall: import("./index.js").HealthStatus;
118
+ healthStatus: Record<string, unknown>;
119
+ source: string;
120
+ lastFullCheck: string | null;
121
+ timestamp: string;
122
+ } | {
123
+ healthStatus: {
124
+ tiktok?: "healthy" | "degraded" | "down" | undefined;
125
+ twitter?: "healthy" | "degraded" | "down" | undefined;
126
+ reddit?: "healthy" | "degraded" | "down" | undefined;
127
+ instagram?: "healthy" | "degraded" | "down" | undefined;
128
+ youtube?: "healthy" | "degraded" | "down" | undefined;
129
+ moltbook?: "healthy" | "degraded" | "down" | undefined;
130
+ xiaohongshu?: "healthy" | "degraded" | "down" | undefined;
131
+ };
132
+ timestamp: string;
133
+ overall?: undefined;
134
+ source?: undefined;
135
+ lastFullCheck?: undefined;
136
+ }>;
137
+ export declare function analyzeContent(service: UnifiedSocialMediaService, args: AnalyzeArgs): Promise<any>;
138
+ export declare function clusterOpinions(service: UnifiedSocialMediaService, args: ClusterArgs): Promise<any>;
139
+ export declare function enrichContent(service: UnifiedSocialMediaService, args: EnrichArgs): Promise<any>;
140
+ export interface DeepAnalyzeArgs {
141
+ platform: string;
142
+ contentId: string;
143
+ analysisDepth: 'deep' | 'comprehensive';
144
+ }
145
+ export interface InsightsArgs {
146
+ platform: string;
147
+ contentId: string;
148
+ categories?: string[];
149
+ }
150
+ export interface ResearchArgs {
151
+ query: string;
152
+ platforms?: string[];
153
+ depth?: 'quick' | 'standard' | 'deep';
154
+ }
155
+ export declare function deepAnalyze(args: DeepAnalyzeArgs): Promise<any>;
156
+ export declare function extractInsights(args: InsightsArgs): Promise<any>;
157
+ export declare function researchSynthesis(args: ResearchArgs): Promise<any>;