slashvibe-mcp 0.2.9 → 0.3.13

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 (205) hide show
  1. package/README.md +1 -0
  2. package/analytics.js +107 -0
  3. package/auth-store.js +148 -0
  4. package/auto-update.js +130 -0
  5. package/bridges/bridge-monitor.js +388 -0
  6. package/bridges/discord-bot.js +431 -0
  7. package/bridges/farcaster.js +299 -0
  8. package/bridges/telegram.js +261 -0
  9. package/bridges/webhook-health.js +420 -0
  10. package/bridges/webhook-server.js +437 -0
  11. package/bridges/whatsapp.js +441 -0
  12. package/bridges/x-webhook.js +423 -0
  13. package/config.js +154 -5
  14. package/crypto.js +3 -8
  15. package/games/arcade.js +406 -0
  16. package/games/chess.js +451 -0
  17. package/games/colorguess.js +343 -0
  18. package/games/crossword-words.js +171 -0
  19. package/games/crossword.js +461 -0
  20. package/games/drawing.js +347 -0
  21. package/games/gameroulette.js +300 -0
  22. package/games/gamerouter.js +336 -0
  23. package/games/gamestatus.js +337 -0
  24. package/games/guessnumber.js +209 -0
  25. package/games/hangman.js +279 -0
  26. package/games/memory.js +338 -0
  27. package/games/multiplayer-tictactoe.js +389 -0
  28. package/games/pixelart.js +399 -0
  29. package/games/quickduel.js +354 -0
  30. package/games/riddle.js +371 -0
  31. package/games/rockpaperscissors.js +291 -0
  32. package/games/snake.js +406 -0
  33. package/games/storybuilder.js +343 -0
  34. package/games/tictactoe.js +345 -0
  35. package/games/twentyquestions.js +286 -0
  36. package/games/twotruths.js +207 -0
  37. package/games/werewolf.js +508 -0
  38. package/games/wordassociation.js +247 -0
  39. package/games/wordchain.js +135 -0
  40. package/index.js +105 -216
  41. package/intelligence/index.js +45 -0
  42. package/intelligence/infer.js +316 -0
  43. package/intelligence/interests.js +369 -0
  44. package/intelligence/patterns.js +651 -0
  45. package/intelligence/proactive.js +358 -0
  46. package/intelligence/serendipity.js +306 -0
  47. package/memory.js +166 -0
  48. package/notification-emitter.js +77 -0
  49. package/notify.js +102 -1
  50. package/package.json +21 -7
  51. package/prompts.js +1 -1
  52. package/protocol/index.js +161 -1
  53. package/setup.js +402 -0
  54. package/store/api.js +528 -82
  55. package/store/profiles.js +160 -12
  56. package/tools/_actions.js +463 -16
  57. package/tools/_deprecated/auto-suggest-connections.js +304 -0
  58. package/tools/_deprecated/bootstrap-skills.js +231 -0
  59. package/tools/_deprecated/bridge-dashboard.js +342 -0
  60. package/tools/_deprecated/bridge-health.js +400 -0
  61. package/tools/_deprecated/bridge-live.js +384 -0
  62. package/tools/_deprecated/bridges.js +383 -0
  63. package/tools/_deprecated/colorguess.js +281 -0
  64. package/tools/_deprecated/discover-insights.js +379 -0
  65. package/tools/_deprecated/discover-momentum.js +256 -0
  66. package/tools/_deprecated/discovery-analytics.js +345 -0
  67. package/tools/_deprecated/discovery-auto-suggest.js +275 -0
  68. package/tools/_deprecated/discovery-bootstrap.js +267 -0
  69. package/tools/_deprecated/discovery-daily.js +375 -0
  70. package/tools/_deprecated/discovery-dashboard.js +385 -0
  71. package/tools/_deprecated/discovery-digest.js +314 -0
  72. package/tools/_deprecated/discovery-hub.js +357 -0
  73. package/tools/_deprecated/discovery-insights.js +384 -0
  74. package/tools/_deprecated/discovery-momentum.js +281 -0
  75. package/tools/_deprecated/discovery-monitor.js +319 -0
  76. package/tools/_deprecated/discovery-proactive.js +300 -0
  77. package/tools/_deprecated/draw.js +317 -0
  78. package/tools/_deprecated/farcaster.js +307 -0
  79. package/tools/_deprecated/forget.js +119 -0
  80. package/tools/_deprecated/games-catalog.js +376 -0
  81. package/tools/_deprecated/games.js +313 -0
  82. package/tools/_deprecated/guessnumber.js +194 -0
  83. package/tools/_deprecated/hangman.js +129 -0
  84. package/tools/_deprecated/multiplayer-tictactoe.js +303 -0
  85. package/tools/_deprecated/recall.js +147 -0
  86. package/tools/_deprecated/remember.js +86 -0
  87. package/tools/_deprecated/riddle.js +240 -0
  88. package/tools/_deprecated/run-bootstrap.js +69 -0
  89. package/tools/_deprecated/skills-analytics.js +349 -0
  90. package/tools/_deprecated/skills-bootstrap.js +301 -0
  91. package/tools/_deprecated/skills-dashboard.js +268 -0
  92. package/tools/_deprecated/skills.js +380 -0
  93. package/tools/_deprecated/smart-intro.js +353 -0
  94. package/tools/_deprecated/storybuilder.js +331 -0
  95. package/tools/_deprecated/telegram-bot.js +183 -0
  96. package/tools/_deprecated/telegram-setup.js +214 -0
  97. package/tools/_deprecated/twentyquestions.js +143 -0
  98. package/tools/_discovery-enhanced.js +290 -0
  99. package/tools/_discovery.js +439 -0
  100. package/tools/_proactive-discovery.js +301 -0
  101. package/tools/_shared/index.js +64 -0
  102. package/tools/_shared.js +234 -0
  103. package/tools/_work-context.js +338 -0
  104. package/tools/_work-context.manual-test.js +199 -0
  105. package/tools/_work-context.test.js +260 -0
  106. package/tools/activity.js +220 -0
  107. package/tools/admin-inbox.js +218 -0
  108. package/tools/agent-treasury.js +288 -0
  109. package/tools/analytics.js +191 -0
  110. package/tools/approve.js +197 -0
  111. package/tools/arcade.js +173 -0
  112. package/tools/artifact-create.js +17 -11
  113. package/tools/artifact-view.js +31 -1
  114. package/tools/artifacts-price.js +47 -43
  115. package/tools/ask-expert.js +160 -0
  116. package/tools/available.js +120 -0
  117. package/tools/become-expert.js +150 -0
  118. package/tools/broadcast.js +286 -0
  119. package/tools/chat.js +202 -0
  120. package/tools/collaborative-drawing.js +286 -0
  121. package/tools/connection-status.js +178 -0
  122. package/tools/crossword.js +17 -1
  123. package/tools/discover.js +350 -34
  124. package/tools/dm.js +115 -73
  125. package/tools/drawing.js +1 -1
  126. package/tools/earnings.js +126 -0
  127. package/tools/echo.js +16 -0
  128. package/tools/feed.js +51 -7
  129. package/tools/follow.js +224 -0
  130. package/tools/friends.js +207 -0
  131. package/tools/game.js +2 -2
  132. package/tools/genesis.js +233 -0
  133. package/tools/gig-browse.js +206 -0
  134. package/tools/gig-complete.js +144 -0
  135. package/tools/handoff.js +7 -1
  136. package/tools/help.js +4 -4
  137. package/tools/idea.js +9 -2
  138. package/tools/inbox.js +334 -28
  139. package/tools/init.js +727 -36
  140. package/tools/invite.js +15 -4
  141. package/tools/l2-bridge.js +272 -0
  142. package/tools/l2-status.js +217 -0
  143. package/tools/l2.js +206 -0
  144. package/tools/migrate.js +156 -0
  145. package/tools/mint.js +377 -0
  146. package/tools/multiplayer-game.js +1 -1
  147. package/tools/notifications.js +415 -0
  148. package/tools/observe.js +200 -0
  149. package/tools/onboarding.js +147 -0
  150. package/tools/open.js +143 -12
  151. package/tools/party-game.js +2 -2
  152. package/tools/plan.js +225 -0
  153. package/tools/presence-agent.js +7 -0
  154. package/tools/proof-of-work.js +100 -104
  155. package/tools/pulse.js +218 -0
  156. package/tools/reply.js +166 -0
  157. package/tools/report.js +2 -2
  158. package/tools/reputation.js +175 -0
  159. package/tools/request.js +17 -3
  160. package/tools/schedule.js +367 -0
  161. package/tools/search-messages.js +123 -0
  162. package/tools/session.js +420 -0
  163. package/tools/session_price.js +128 -0
  164. package/tools/settings.js +126 -3
  165. package/tools/ship.js +31 -8
  166. package/tools/shipback.js +326 -0
  167. package/tools/smart-check.js +201 -0
  168. package/tools/social-processor.js +445 -0
  169. package/tools/solo-game.js +1 -1
  170. package/tools/start.js +335 -93
  171. package/tools/status.js +53 -6
  172. package/tools/stuck.js +297 -0
  173. package/tools/subscribe.js +148 -0
  174. package/tools/subscriptions.js +134 -0
  175. package/tools/suggest-tags.js +6 -8
  176. package/tools/tag-suggestions.js +257 -0
  177. package/tools/tip.js +193 -0
  178. package/tools/token.js +103 -0
  179. package/tools/update.js +1 -1
  180. package/tools/wallet.js +239 -186
  181. package/tools/watch.js +157 -0
  182. package/tools/webhook-test.js +388 -0
  183. package/tools/who.js +54 -3
  184. package/tools/withdraw.js +145 -0
  185. package/tools/work-summary.js +96 -0
  186. package/tools/workshop.js +327 -0
  187. package/tools/x-mentions.js +1 -1
  188. package/version.json +14 -3
  189. package/tools/artifacts-buy.js +0 -111
  190. package/tools/connect.js +0 -284
  191. package/tools/gigs-apply.js +0 -99
  192. package/tools/gigs-browse.js +0 -114
  193. package/tools/gigs-complete.js +0 -128
  194. package/tools/gigs-post.js +0 -140
  195. package/tools/live-off.js +0 -109
  196. package/tools/live-watch.js +0 -176
  197. package/tools/live.js +0 -128
  198. package/tools/sessions-browse.js +0 -105
  199. package/tools/whats-happening.js +0 -125
  200. /package/tools/{away.js → _deprecated/away.js} +0 -0
  201. /package/tools/{back.js → _deprecated/back.js} +0 -0
  202. /package/tools/{mute.js → _deprecated/mute.js} +0 -0
  203. /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
  204. /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
  205. /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
@@ -0,0 +1,345 @@
1
+ /**
2
+ * vibe discovery-analytics — Community Discovery Insights
3
+ *
4
+ * Provides analytics and insights about the /vibe community to help
5
+ * understand connection patterns, popular interests, and opportunities
6
+ * for better matching.
7
+ *
8
+ * Commands:
9
+ * - discovery-analytics overview — Community overview and trends
10
+ * - discovery-analytics gaps — Identify connection opportunities
11
+ * - discovery-analytics popular — Most popular interests and skills
12
+ * - discovery-analytics lonely — People who might need connections
13
+ */
14
+
15
+ const config = require('../config');
16
+ const userProfiles = require('../store/profiles');
17
+ const { formatTimeAgo, requireInit } = require('./_shared');
18
+
19
+ const definition = {
20
+ name: 'vibe_discovery_analytics',
21
+ description: 'Analytics and insights about community discovery patterns.',
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ command: {
26
+ type: 'string',
27
+ enum: ['overview', 'gaps', 'popular', 'lonely'],
28
+ description: 'Analytics command to run'
29
+ }
30
+ }
31
+ }
32
+ };
33
+
34
+ // Calculate community health metrics
35
+ async function getCommunityHealth() {
36
+ const profiles = await userProfiles.getAllProfiles();
37
+ const totalUsers = profiles.length;
38
+
39
+ // Profile completeness
40
+ const withBuilding = profiles.filter(p => p.building).length;
41
+ const withInterests = profiles.filter(p => p.interests && p.interests.length > 0).length;
42
+ const withTags = profiles.filter(p => p.tags && p.tags.length > 0).length;
43
+ const withConnections = profiles.filter(p => p.connections && p.connections.length > 0).length;
44
+
45
+ // Activity levels
46
+ const now = Date.now();
47
+ const day = 24 * 60 * 60 * 1000;
48
+ const week = 7 * day;
49
+
50
+ const activeToday = profiles.filter(p => p.lastSeen && (now - p.lastSeen) < day).length;
51
+ const activeThisWeek = profiles.filter(p => p.lastSeen && (now - p.lastSeen) < week).length;
52
+
53
+ return {
54
+ totalUsers,
55
+ profileCompleteness: {
56
+ withBuilding: Math.round((withBuilding / totalUsers) * 100),
57
+ withInterests: Math.round((withInterests / totalUsers) * 100),
58
+ withTags: Math.round((withTags / totalUsers) * 100),
59
+ withConnections: Math.round((withConnections / totalUsers) * 100)
60
+ },
61
+ activity: {
62
+ activeToday,
63
+ activeThisWeek,
64
+ todayRate: Math.round((activeToday / totalUsers) * 100),
65
+ weekRate: Math.round((activeThisWeek / totalUsers) * 100)
66
+ }
67
+ };
68
+ }
69
+
70
+ // Find potential connection gaps
71
+ async function findConnectionGaps() {
72
+ const profiles = await userProfiles.getAllProfiles();
73
+ const gaps = [];
74
+
75
+ // Find isolated users (no connections)
76
+ const isolated = profiles.filter(p => !p.connections || p.connections.length === 0);
77
+
78
+ // Find skill gaps (people with complementary skills who haven't connected)
79
+ const skillPairs = [
80
+ ['frontend', 'backend'],
81
+ ['design', 'engineering'],
82
+ ['ai', 'data'],
83
+ ['product', 'engineering'],
84
+ ['research', 'implementation'],
85
+ ['marketing', 'product']
86
+ ];
87
+
88
+ const missedConnections = [];
89
+ for (const [skill1, skill2] of skillPairs) {
90
+ const group1 = profiles.filter(p => p.tags?.includes(skill1));
91
+ const group2 = profiles.filter(p => p.tags?.includes(skill2));
92
+
93
+ for (const user1 of group1) {
94
+ for (const user2 of group2) {
95
+ if (user1.handle !== user2.handle) {
96
+ const connected = await userProfiles.hasBeenConnected(user1.handle, user2.handle);
97
+ if (!connected) {
98
+ missedConnections.push({
99
+ user1: user1.handle,
100
+ user2: user2.handle,
101
+ reason: `${skill1} + ${skill2} collaboration potential`,
102
+ user1Building: user1.building,
103
+ user2Building: user2.building
104
+ });
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ return {
112
+ isolated: isolated.slice(0, 5),
113
+ missedConnections: missedConnections.slice(0, 8)
114
+ };
115
+ }
116
+
117
+ // Find people who might need more connections
118
+ async function findLonelyUsers() {
119
+ const profiles = await userProfiles.getAllProfiles();
120
+ const now = Date.now();
121
+ const week = 7 * 24 * 60 * 60 * 1000;
122
+
123
+ const lonely = profiles.filter(p => {
124
+ const recentlyActive = p.lastSeen && (now - p.lastSeen) < week;
125
+ const hasProfile = p.building || (p.interests && p.interests.length > 0);
126
+ const fewConnections = !p.connections || p.connections.length < 2;
127
+
128
+ return recentlyActive && hasProfile && fewConnections;
129
+ });
130
+
131
+ return lonely.sort((a, b) => (b.lastSeen || 0) - (a.lastSeen || 0)).slice(0, 5);
132
+ }
133
+
134
+ // Analyze interest and skill trends
135
+ async function analyzePopularTrends() {
136
+ const trendingInterests = await userProfiles.getTrendingInterests();
137
+ const trendingTags = await userProfiles.getTrendingTags();
138
+
139
+ // Calculate growth potential
140
+ const profiles = await userProfiles.getAllProfiles();
141
+ const interestCombos = {};
142
+
143
+ for (const profile of profiles) {
144
+ if (profile.interests && profile.interests.length > 1) {
145
+ for (let i = 0; i < profile.interests.length; i++) {
146
+ for (let j = i + 1; j < profile.interests.length; j++) {
147
+ const combo = [profile.interests[i], profile.interests[j]].sort().join(' + ');
148
+ interestCombos[combo] = (interestCombos[combo] || 0) + 1;
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ const topCombos = Object.entries(interestCombos)
155
+ .sort(([,a], [,b]) => b - a)
156
+ .slice(0, 5)
157
+ .map(([combo, count]) => ({ combo, count }));
158
+
159
+ return {
160
+ interests: trendingInterests,
161
+ skills: trendingTags,
162
+ interestCombos: topCombos
163
+ };
164
+ }
165
+
166
+ async function handler(args) {
167
+ const initCheck = requireInit();
168
+ if (initCheck) return initCheck;
169
+
170
+ const command = args.command || 'overview';
171
+ let display = '';
172
+
173
+ try {
174
+ switch (command) {
175
+ case 'overview': {
176
+ const health = await getCommunityHealth();
177
+ const trends = await analyzePopularTrends();
178
+
179
+ display = `## /vibe Discovery Analytics 📊\n\n`;
180
+
181
+ display += `### Community Health\n`;
182
+ display += `**Total Users:** ${health.totalUsers}\n`;
183
+ display += `**Active Today:** ${health.activity.activeToday} (${health.activity.todayRate}%)\n`;
184
+ display += `**Active This Week:** ${health.activity.activeThisWeek} (${health.activity.weekRate}%)\n\n`;
185
+
186
+ display += `### Profile Completeness\n`;
187
+ display += `**Building Projects:** ${health.profileCompleteness.withBuilding}%\n`;
188
+ display += `**Have Interests:** ${health.profileCompleteness.withInterests}%\n`;
189
+ display += `**Have Skills:** ${health.profileCompleteness.withTags}%\n`;
190
+ display += `**Made Connections:** ${health.profileCompleteness.withConnections}%\n\n`;
191
+
192
+ display += `### Trending Now\n`;
193
+ if (trends.interests.length > 0) {
194
+ display += `**Top Interests:** ${trends.interests.slice(0, 3).map(i => `${i.interest} (${i.count})`).join(', ')}\n`;
195
+ }
196
+ if (trends.skills.length > 0) {
197
+ display += `**Hot Skills:** ${trends.skills.slice(0, 3).map(s => `${s.tag} (${s.count})`).join(', ')}\n`;
198
+ }
199
+
200
+ if (trends.interestCombos.length > 0) {
201
+ display += `**Interest Combos:** ${trends.interestCombos.slice(0, 2).map(c => c.combo).join(', ')}\n`;
202
+ }
203
+
204
+ display += `\n**Commands:**\n`;
205
+ display += `• \`discovery-analytics gaps\` — Find connection opportunities\n`;
206
+ display += `• \`discovery-analytics popular\` — See detailed trends\n`;
207
+ display += `• \`discovery-analytics lonely\` — People who need connections`;
208
+ break;
209
+ }
210
+
211
+ case 'gaps': {
212
+ const gaps = await findConnectionGaps();
213
+
214
+ display = `## Connection Opportunities 🔗\n\n`;
215
+
216
+ if (gaps.isolated.length > 0) {
217
+ display += `### New Users Need Connections\n`;
218
+ for (const user of gaps.isolated) {
219
+ display += `**@${user.handle}**\n`;
220
+ display += `${user.building || 'Ready to connect'}\n`;
221
+ if (user.interests && user.interests.length > 0) {
222
+ display += `Interests: ${user.interests.join(', ')}\n`;
223
+ }
224
+ if (user.tags && user.tags.length > 0) {
225
+ display += `Skills: ${user.tags.join(', ')}\n`;
226
+ }
227
+ display += `_Last seen: ${formatTimeAgo(user.lastSeen)}_\n\n`;
228
+ }
229
+ }
230
+
231
+ if (gaps.missedConnections.length > 0) {
232
+ display += `### Missed Collaboration Opportunities\n`;
233
+ for (const missed of gaps.missedConnections.slice(0, 5)) {
234
+ display += `**@${missed.user1}** + **@${missed.user2}**\n`;
235
+ display += `${missed.reason}\n`;
236
+ if (missed.user1Building) display += `• ${missed.user1}: ${missed.user1Building}\n`;
237
+ if (missed.user2Building) display += `• ${missed.user2}: ${missed.user2Building}\n`;
238
+ display += `\n`;
239
+ }
240
+
241
+ display += `**Suggest these connections:**\n`;
242
+ display += `\`suggest-connection @${gaps.missedConnections[0].user1} @${gaps.missedConnections[0].user2} "${gaps.missedConnections[0].reason}"\``;
243
+ }
244
+
245
+ if (gaps.isolated.length === 0 && gaps.missedConnections.length === 0) {
246
+ display = `## All Connected! 🎉\n\nNo obvious connection gaps found. The community is well-connected!\n\n**Keep growing:**\n• Encourage more profile completion\n• Watch for new joiners to welcome`;
247
+ }
248
+ break;
249
+ }
250
+
251
+ case 'popular': {
252
+ const trends = await analyzePopularTrends();
253
+
254
+ display = `## Community Trends 📈\n\n`;
255
+
256
+ if (trends.interests.length > 0) {
257
+ display += `### Popular Interests\n`;
258
+ for (const trend of trends.interests) {
259
+ display += `**${trend.interest}** — ${trend.count} people\n`;
260
+ }
261
+ display += `\n`;
262
+ }
263
+
264
+ if (trends.skills.length > 0) {
265
+ display += `### Top Skills\n`;
266
+ for (const skill of trends.skills) {
267
+ display += `**${skill.tag}** — ${skill.count} people\n`;
268
+ }
269
+ display += `\n`;
270
+ }
271
+
272
+ if (trends.interestCombos.length > 0) {
273
+ display += `### Interest Combinations\n`;
274
+ for (const combo of trends.interestCombos) {
275
+ display += `**${combo.combo}** — ${combo.count} people\n`;
276
+ }
277
+ display += `\n`;
278
+ }
279
+
280
+ display += `**Discovery Opportunities:**\n`;
281
+ display += `• Create interest groups for popular topics\n`;
282
+ display += `• Host skill-sharing sessions\n`;
283
+ display += `• Encourage rare skill/interest combinations`;
284
+ break;
285
+ }
286
+
287
+ case 'lonely': {
288
+ const lonely = await findLonelyUsers();
289
+
290
+ if (lonely.length === 0) {
291
+ display = `## Everyone's Connected! 🤝\n\nNo lonely users found. The community is doing great at connecting!\n\n**Keep it up:**\n• Welcome new joiners quickly\n• Encourage profile completion\n• Celebrate successful connections`;
292
+ } else {
293
+ display = `## People Who Could Use More Connections\n\n`;
294
+
295
+ for (const user of lonely) {
296
+ display += `**@${user.handle}**\n`;
297
+ display += `${user.building || 'Available to connect'}\n`;
298
+
299
+ if (user.interests && user.interests.length > 0) {
300
+ display += `Interests: ${user.interests.join(', ')}\n`;
301
+ }
302
+
303
+ if (user.tags && user.tags.length > 0) {
304
+ display += `Skills: ${user.tags.join(', ')}\n`;
305
+ }
306
+
307
+ const connectionCount = (user.connections || []).length;
308
+ display += `Connections: ${connectionCount}\n`;
309
+ display += `_Last seen: ${formatTimeAgo(user.lastSeen)}_\n\n`;
310
+ }
311
+
312
+ display += `**Suggested Actions:**\n`;
313
+ display += `• Proactively introduce them to similar builders\n`;
314
+ display += `• Include them in group conversations\n`;
315
+ display += `• Share their projects on the board`;
316
+ }
317
+ break;
318
+ }
319
+
320
+ default:
321
+ display = `## Discovery Analytics Commands
322
+
323
+ **\`discovery-analytics overview\`** — Community health and trends
324
+ **\`discovery-analytics gaps\`** — Find connection opportunities
325
+ **\`discovery-analytics popular\`** — Most popular interests and skills
326
+ **\`discovery-analytics lonely\`** — People who might need connections
327
+
328
+ **Use these insights to:**
329
+ - Identify people who need connections
330
+ - Find missed collaboration opportunities
331
+ - Understand community trends
332
+ - Guide future discovery improvements`;
333
+ }
334
+ } catch (error) {
335
+ display = `## Analytics Error
336
+
337
+ ${error.message}
338
+
339
+ Try: \`discovery-analytics\` for available commands`;
340
+ }
341
+
342
+ return { display };
343
+ }
344
+
345
+ module.exports = { definition, handler };
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Discovery Auto Suggest — Intelligent automatic connection suggestions
3
+ *
4
+ * This tool runs automatically when the discovery agent is active to:
5
+ * - Find high-quality matches based on recent activity
6
+ * - Send personalized connection suggestions
7
+ * - Track suggestion success rates
8
+ * - Optimize timing for better engagement
9
+ */
10
+
11
+ const config = require('../config');
12
+ const userProfiles = require('../store/profiles');
13
+ const store = require('../store');
14
+ const { formatTimeAgo } = require('./_shared');
15
+ const smartIntro = require('./smart-intro');
16
+
17
+ // Calculate enhanced match score with behavioral signals
18
+ function calculateEnhancedMatchScore(user1, user2) {
19
+ let score = 0;
20
+ const reasons = [];
21
+
22
+ // Core similarity scoring
23
+ if (user1.interests && user2.interests) {
24
+ const shared = user1.interests.filter(i =>
25
+ user2.interests.some(j => i.toLowerCase() === j.toLowerCase())
26
+ );
27
+ if (shared.length > 0) {
28
+ score += shared.length * 15;
29
+ reasons.push(`Shared interests: ${shared.slice(0, 2).join(', ')}`);
30
+ }
31
+ }
32
+
33
+ if (user1.tags && user2.tags) {
34
+ const sharedTags = user1.tags.filter(t =>
35
+ user2.tags.some(u => t.toLowerCase() === u.toLowerCase())
36
+ );
37
+ if (sharedTags.length > 0) {
38
+ score += sharedTags.length * 12;
39
+ reasons.push(`Similar skills: ${sharedTags.slice(0, 2).join(', ')}`);
40
+ }
41
+ }
42
+
43
+ // Building similarity
44
+ if (user1.building && user2.building) {
45
+ const building1 = user1.building.toLowerCase();
46
+ const building2 = user2.building.toLowerCase();
47
+ const words1 = building1.split(/\s+/);
48
+ const words2 = building2.split(/\s+/);
49
+ const overlap = words1.filter(w => words2.includes(w) && w.length > 3);
50
+ if (overlap.length > 0) {
51
+ score += overlap.length * 10;
52
+ reasons.push(`Both working on ${overlap[0]}`);
53
+ }
54
+ }
55
+
56
+ // Complementary skills bonus
57
+ const complementaryPairs = [
58
+ [['frontend', 'react', 'ui'], ['backend', 'api', 'server']],
59
+ [['design', 'figma', 'ui'], ['frontend', 'development']],
60
+ [['ai', 'ml'], ['data', 'python', 'backend']],
61
+ [['product', 'pm'], ['engineering', 'technical']],
62
+ [['mobile', 'ios', 'android'], ['backend', 'api']]
63
+ ];
64
+
65
+ for (const [group1, group2] of complementaryPairs) {
66
+ const hasGroup1 = user1.tags?.some(t =>
67
+ group1.some(g => t.toLowerCase().includes(g))
68
+ );
69
+ const hasGroup2 = user2.tags?.some(t =>
70
+ group2.some(g => t.toLowerCase().includes(g))
71
+ );
72
+
73
+ if ((hasGroup1 && hasGroup2) || (user1.tags?.some(t => group2.some(g => t.toLowerCase().includes(g))) && user2.tags?.some(t => group1.some(g => t.toLowerCase().includes(g))))) {
74
+ score += 18; // Higher weight for complementary skills
75
+ reasons.push('Complementary skills - great collaboration potential');
76
+ break;
77
+ }
78
+ }
79
+
80
+ // Recent activity boost
81
+ const now = Date.now();
82
+ const oneWeekAgo = now - (7 * 24 * 60 * 60 * 1000);
83
+
84
+ if (user1.lastSeen && user1.lastSeen > oneWeekAgo &&
85
+ user2.lastSeen && user2.lastSeen > oneWeekAgo) {
86
+ score += 8;
87
+ reasons.push('Both recently active');
88
+ }
89
+
90
+ // Shipping activity boost
91
+ if (user1.ships?.length > 0 && user2.ships?.length > 0) {
92
+ const recentShips = user1.ships.filter(s => s.timestamp > oneWeekAgo).length +
93
+ user2.ships.filter(s => s.timestamp > oneWeekAgo).length;
94
+ if (recentShips > 0) {
95
+ score += recentShips * 5;
96
+ reasons.push('Active builders');
97
+ }
98
+ }
99
+
100
+ return { score, reasons: reasons.slice(0, 3) };
101
+ }
102
+
103
+ // Find the best potential matches for a user
104
+ async function findBestMatches(targetHandle, limit = 3) {
105
+ const targetProfile = await userProfiles.getProfile(targetHandle);
106
+ const allProfiles = await userProfiles.getAllProfiles();
107
+
108
+ const candidates = allProfiles.filter(p =>
109
+ p.handle !== targetHandle &&
110
+ p.building &&
111
+ (p.interests?.length > 0 || p.tags?.length > 0)
112
+ );
113
+
114
+ const matches = [];
115
+
116
+ for (const candidate of candidates) {
117
+ // Skip if already connected
118
+ const alreadyConnected = await userProfiles.hasBeenConnected(targetHandle, candidate.handle);
119
+ if (alreadyConnected) continue;
120
+
121
+ const match = calculateEnhancedMatchScore(targetProfile, candidate);
122
+ if (match.score >= 20) { // Higher threshold for auto-suggestions
123
+ matches.push({
124
+ handle: candidate.handle,
125
+ score: match.score,
126
+ reasons: match.reasons,
127
+ building: candidate.building,
128
+ interests: candidate.interests || [],
129
+ tags: candidate.tags || [],
130
+ lastSeen: candidate.lastSeen
131
+ });
132
+ }
133
+ }
134
+
135
+ return matches.sort((a, b) => b.score - a.score).slice(0, limit);
136
+ }
137
+
138
+ // Generate and queue connection suggestions
139
+ async function generateSuggestions() {
140
+ const profiles = await userProfiles.getAllProfiles();
141
+ const now = Date.now();
142
+ const oneWeekAgo = now - (7 * 24 * 60 * 60 * 1000);
143
+
144
+ // Focus on recently active users with complete profiles
145
+ const activeUsers = profiles.filter(p =>
146
+ p.lastSeen && p.lastSeen > oneWeekAgo &&
147
+ p.building && p.interests?.length > 0
148
+ );
149
+
150
+ if (activeUsers.length < 2) {
151
+ return {
152
+ success: false,
153
+ reason: 'Not enough active users with complete profiles',
154
+ activeUsers: activeUsers.length
155
+ };
156
+ }
157
+
158
+ const suggestions = [];
159
+
160
+ for (const user of activeUsers) {
161
+ const matches = await findBestMatches(user.handle, 2);
162
+
163
+ for (const match of matches) {
164
+ // Generate personalized introduction
165
+ const intro = await smartIntro.generateIntroduction(user.handle, match.handle, 'casual');
166
+
167
+ suggestions.push({
168
+ from: user.handle,
169
+ to: match.handle,
170
+ score: match.score,
171
+ reasons: match.reasons,
172
+ introduction: intro,
173
+ timestamp: now
174
+ });
175
+ }
176
+ }
177
+
178
+ return {
179
+ success: true,
180
+ suggestions: suggestions.sort((a, b) => b.score - a.score).slice(0, 8),
181
+ totalGenerated: suggestions.length
182
+ };
183
+ }
184
+
185
+ // Intelligent timing for connection suggestions
186
+ function getOptimalSuggestionTime() {
187
+ const now = new Date();
188
+ const currentHour = now.getHours();
189
+ const currentDay = now.getDay();
190
+
191
+ // Best times based on typical developer/builder activity:
192
+ // Weekdays: 9-11am, 2-4pm, 7-9pm
193
+ // Weekends: 10am-12pm, 2-5pm
194
+
195
+ const isWeekend = currentDay === 0 || currentDay === 6;
196
+ const goodTimes = isWeekend
197
+ ? [10, 11, 14, 15, 16, 17]
198
+ : [9, 10, 14, 15, 19, 20];
199
+
200
+ const isOptimalTime = goodTimes.includes(currentHour);
201
+
202
+ return {
203
+ isOptimal: isOptimalTime,
204
+ currentHour,
205
+ recommendation: isOptimalTime
206
+ ? 'Good time for connection suggestions'
207
+ : `Better to suggest connections at ${goodTimes.join(', ')} o'clock`
208
+ };
209
+ }
210
+
211
+ // Track suggestion success (when users actually connect)
212
+ async function trackSuggestionSuccess(fromHandle, toHandle, successful) {
213
+ // This would integrate with actual connection tracking
214
+ // For now, we'll update connection records
215
+
216
+ if (successful) {
217
+ await userProfiles.recordConnection(
218
+ fromHandle,
219
+ toHandle,
220
+ 'Auto-suggested connection - successful match'
221
+ );
222
+ }
223
+
224
+ return { tracked: true, successful };
225
+ }
226
+
227
+ // Main auto-suggestion workflow
228
+ async function runAutoSuggestions() {
229
+ const startTime = Date.now();
230
+
231
+ // Check if it's a good time for suggestions
232
+ const timing = getOptimalSuggestionTime();
233
+
234
+ // Generate suggestions
235
+ const result = await generateSuggestions();
236
+
237
+ if (!result.success) {
238
+ return {
239
+ success: false,
240
+ message: result.reason,
241
+ timing: timing.recommendation,
242
+ duration: Date.now() - startTime
243
+ };
244
+ }
245
+
246
+ const summary = {
247
+ success: true,
248
+ suggestionsGenerated: result.suggestions.length,
249
+ highQualityMatches: result.suggestions.filter(s => s.score >= 35).length,
250
+ topSuggestion: result.suggestions[0] || null,
251
+ timing: timing.isOptimal ? 'optimal' : 'suboptimal',
252
+ duration: Date.now() - startTime
253
+ };
254
+
255
+ // If it's optimal timing, we could actually send the suggestions
256
+ if (timing.isOptimal && result.suggestions.length > 0) {
257
+ summary.recommendedAction = 'Send top 3 suggestions to users';
258
+ summary.topSuggestions = result.suggestions.slice(0, 3).map(s => ({
259
+ connection: `@${s.from} → @${s.to}`,
260
+ score: s.score,
261
+ primaryReason: s.reasons[0] || 'Good match'
262
+ }));
263
+ }
264
+
265
+ return summary;
266
+ }
267
+
268
+ module.exports = {
269
+ calculateEnhancedMatchScore,
270
+ findBestMatches,
271
+ generateSuggestions,
272
+ getOptimalSuggestionTime,
273
+ trackSuggestionSuccess,
274
+ runAutoSuggestions
275
+ };