slashvibe-mcp 0.3.21 → 0.3.23
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.
- package/LICENSE +21 -0
- package/README.md +280 -47
- package/auto-update.js +10 -15
- package/config.js +36 -31
- package/crypto.js +1 -6
- package/debug.js +12 -0
- package/discord.js +19 -19
- package/eslint.config.js +54 -0
- package/index.js +217 -207
- package/intelligence/index.js +2 -9
- package/intelligence/infer.js +10 -16
- package/intelligence/patterns.js +23 -18
- package/intelligence/proactive.js +16 -15
- package/intelligence/serendipity.js +57 -20
- package/memory.js +13 -8
- package/migrate-v2.js +72 -0
- package/notification-emitter.js +2 -2
- package/notify.js +39 -14
- package/package.json +28 -29
- package/post-install.js +141 -0
- package/presence.js +2 -2
- package/prompts.js +5 -9
- package/protocol/index.js +123 -87
- package/protocol/telegram-commands.js +36 -37
- package/store/api.js +358 -529
- package/store/local.js +9 -10
- package/store/profiles.js +48 -192
- package/store/reservations.js +2 -9
- package/store/skills.js +69 -71
- package/store/sqlite.js +355 -0
- package/test-skills-bootstrap.js +20 -0
- package/test-v2-integration.js +385 -0
- package/tools/_actions.js +48 -387
- package/tools/_connection-queue.js +45 -56
- package/tools/_discovery-enhanced.js +52 -57
- package/tools/_discovery.js +87 -185
- package/tools/{l2-status.js → _experimental/l2-status.js} +68 -70
- package/tools/{shipback.js → _experimental/shipback.js} +4 -3
- package/tools/_proactive-discovery.js +60 -73
- package/tools/_shared/index.js +41 -64
- package/tools/admin-inbox.js +10 -15
- package/tools/agents.js +1 -1
- package/tools/artifact-create.js +13 -23
- package/tools/artifact-view.js +4 -4
- package/tools/{_deprecated/back.js → back.js} +1 -1
- package/tools/bye.js +3 -5
- package/tools/consent.js +2 -2
- package/tools/context.js +9 -10
- package/tools/crossword.js +3 -2
- package/tools/discover.js +94 -356
- package/tools/dm.js +27 -86
- package/tools/doctor.js +12 -41
- package/tools/drawing.js +34 -20
- package/tools/echo.js +11 -11
- package/tools/feed.js +30 -58
- package/tools/follow.js +64 -187
- package/tools/{_deprecated/forget.js → forget.js} +4 -7
- package/tools/game.js +144 -48
- package/tools/handoff.js +6 -8
- package/tools/help.js +3 -3
- package/tools/idea.js +15 -27
- package/tools/inbox.js +121 -293
- package/tools/init.js +54 -151
- package/tools/invite.js +8 -21
- package/tools/migrate.js +27 -24
- package/tools/multiplayer-game.js +50 -40
- package/tools/{_deprecated/mute.js → mute.js} +4 -3
- package/tools/notifications.js +58 -48
- package/tools/observe.js +12 -15
- package/tools/onboarding.js +8 -11
- package/tools/open.js +13 -144
- package/tools/party-game.js +23 -12
- package/tools/patterns.js +2 -1
- package/tools/ping.js +5 -7
- package/tools/react.js +28 -30
- package/tools/{_deprecated/recall.js → recall.js} +5 -10
- package/tools/release.js +4 -2
- package/tools/{_deprecated/remember.js → remember.js} +4 -6
- package/tools/report.js +2 -2
- package/tools/request.js +6 -26
- package/tools/reserve.js +1 -1
- package/tools/session-fork.js +97 -0
- package/tools/session-save.js +109 -0
- package/tools/settings.js +30 -99
- package/tools/ship.js +74 -56
- package/tools/{_deprecated/skills-exchange.js → skills-exchange.js} +38 -39
- package/tools/social-inbox.js +22 -28
- package/tools/social-post.js +24 -27
- package/tools/solo-game.js +54 -46
- package/tools/start.js +14 -148
- package/tools/status.js +21 -68
- package/tools/submit.js +4 -2
- package/tools/suggest-tags.js +36 -33
- package/tools/summarize.js +19 -16
- package/tools/tag-suggestions.js +72 -73
- package/tools/test.js +1 -1
- package/tools/{_deprecated/tictactoe.js → tictactoe.js} +26 -26
- package/tools/token.js +4 -4
- package/tools/update.js +1 -2
- package/tools/watch.js +132 -112
- package/tools/who.js +20 -40
- package/tools/{_deprecated/wordassociation.js → wordassociation.js} +23 -20
- package/tools/workshop-buddy.js +52 -53
- package/tools/x-mentions.js +0 -1
- package/tools/x-reply.js +0 -1
- package/twitter.js +14 -20
- package/version.json +8 -10
- package/webhook-runner.js +132 -0
- package/auth-store.js +0 -148
- package/bridges/bridge-monitor.js +0 -388
- package/bridges/discord-bot.js +0 -431
- package/bridges/farcaster.js +0 -299
- package/bridges/telegram.js +0 -261
- package/bridges/webhook-health.js +0 -420
- package/bridges/webhook-server.js +0 -437
- package/bridges/whatsapp.js +0 -441
- package/bridges/x-webhook.js +0 -423
- package/games/arcade.js +0 -406
- package/games/chess.js +0 -451
- package/games/colorguess.js +0 -343
- package/games/crossword-words.js +0 -171
- package/games/crossword.js +0 -461
- package/games/drawing.js +0 -347
- package/games/gameroulette.js +0 -300
- package/games/gamerouter.js +0 -336
- package/games/gamestatus.js +0 -337
- package/games/guessnumber.js +0 -209
- package/games/hangman.js +0 -279
- package/games/memory.js +0 -338
- package/games/multiplayer-tictactoe.js +0 -389
- package/games/pixelart.js +0 -399
- package/games/quickduel.js +0 -354
- package/games/riddle.js +0 -371
- package/games/rockpaperscissors.js +0 -291
- package/games/snake.js +0 -406
- package/games/storybuilder.js +0 -343
- package/games/tictactoe.js +0 -345
- package/games/twentyquestions.js +0 -286
- package/games/twotruths.js +0 -207
- package/games/werewolf.js +0 -508
- package/games/wordassociation.js +0 -247
- package/games/wordchain.js +0 -135
- package/intelligence/interests.js +0 -369
- package/setup.js +0 -480
- package/smart-inbox.js +0 -276
- package/tools/_deprecated/auto-suggest-connections.js +0 -304
- package/tools/_deprecated/bootstrap-skills.js +0 -231
- package/tools/_deprecated/bridge-dashboard.js +0 -342
- package/tools/_deprecated/bridge-health.js +0 -400
- package/tools/_deprecated/bridge-live.js +0 -384
- package/tools/_deprecated/bridges.js +0 -383
- package/tools/_deprecated/colorguess.js +0 -281
- package/tools/_deprecated/discover-insights.js +0 -379
- package/tools/_deprecated/discover-momentum.js +0 -256
- package/tools/_deprecated/discovery-analytics.js +0 -345
- package/tools/_deprecated/discovery-auto-suggest.js +0 -275
- package/tools/_deprecated/discovery-bootstrap.js +0 -267
- package/tools/_deprecated/discovery-daily.js +0 -375
- package/tools/_deprecated/discovery-dashboard.js +0 -385
- package/tools/_deprecated/discovery-digest.js +0 -314
- package/tools/_deprecated/discovery-hub.js +0 -357
- package/tools/_deprecated/discovery-insights.js +0 -384
- package/tools/_deprecated/discovery-momentum.js +0 -281
- package/tools/_deprecated/discovery-monitor.js +0 -319
- package/tools/_deprecated/discovery-proactive.js +0 -300
- package/tools/_deprecated/draw.js +0 -317
- package/tools/_deprecated/farcaster.js +0 -307
- package/tools/_deprecated/games-catalog.js +0 -376
- package/tools/_deprecated/games.js +0 -313
- package/tools/_deprecated/guessnumber.js +0 -194
- package/tools/_deprecated/hangman.js +0 -129
- package/tools/_deprecated/multiplayer-tictactoe.js +0 -303
- package/tools/_deprecated/riddle.js +0 -240
- package/tools/_deprecated/run-bootstrap.js +0 -69
- package/tools/_deprecated/skills-analytics.js +0 -349
- package/tools/_deprecated/skills-bootstrap.js +0 -301
- package/tools/_deprecated/skills-dashboard.js +0 -268
- package/tools/_deprecated/skills.js +0 -380
- package/tools/_deprecated/smart-intro.js +0 -353
- package/tools/_deprecated/storybuilder.js +0 -331
- package/tools/_deprecated/telegram-bot.js +0 -183
- package/tools/_deprecated/telegram-setup.js +0 -214
- package/tools/_deprecated/twentyquestions.js +0 -143
- package/tools/_shared.js +0 -234
- package/tools/_work-context.js +0 -338
- package/tools/_work-context.manual-test.js +0 -199
- package/tools/_work-context.test.js +0 -260
- package/tools/activity.js +0 -220
- package/tools/agent-treasury.js +0 -288
- package/tools/analytics.js +0 -191
- package/tools/approve.js +0 -197
- package/tools/arcade.js +0 -173
- package/tools/artifacts-price.js +0 -107
- package/tools/ask-expert.js +0 -160
- package/tools/available.js +0 -120
- package/tools/become-expert.js +0 -150
- package/tools/broadcast.js +0 -325
- package/tools/chat.js +0 -202
- package/tools/collaborative-drawing.js +0 -286
- package/tools/connection-status.js +0 -178
- package/tools/earnings.js +0 -126
- package/tools/friends.js +0 -207
- package/tools/genesis.js +0 -233
- package/tools/gig-browse.js +0 -206
- package/tools/gig-complete.js +0 -144
- package/tools/health.js +0 -87
- package/tools/leaderboard.js +0 -117
- package/tools/lib/git-apply.js +0 -206
- package/tools/lib/git-bundle.js +0 -407
- package/tools/mint.js +0 -377
- package/tools/plan.js +0 -225
- package/tools/profile.js +0 -219
- package/tools/proof-of-work.js +0 -144
- package/tools/pulse.js +0 -218
- package/tools/reply.js +0 -166
- package/tools/reputation.js +0 -175
- package/tools/schedule.js +0 -367
- package/tools/search-messages.js +0 -123
- package/tools/session.js +0 -467
- package/tools/session_price.js +0 -128
- package/tools/smart-check.js +0 -201
- package/tools/social-processor.js +0 -445
- package/tools/streak.js +0 -147
- package/tools/stuck.js +0 -297
- package/tools/subscribe.js +0 -148
- package/tools/subscriptions.js +0 -134
- package/tools/tip.js +0 -193
- package/tools/wallet.js +0 -269
- package/tools/webhook-test.js +0 -388
- package/tools/withdraw.js +0 -145
- package/tools/work-summary.js +0 -96
- package/tools/workshop.js +0 -327
- /package/tools/{l2-bridge.js → _experimental/l2-bridge.js} +0 -0
- /package/tools/{l2.js → _experimental/l2.js} +0 -0
- /package/tools/{_deprecated/away.js → away.js} +0 -0
|
@@ -1,345 +0,0 @@
|
|
|
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 };
|
|
@@ -1,275 +0,0 @@
|
|
|
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
|
-
};
|