slashvibe-mcp 0.3.20 → 0.3.21
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/README.md +47 -252
- package/analytics.js +107 -0
- package/auth-store.js +148 -0
- package/auto-update.js +130 -0
- package/bridges/bridge-monitor.js +388 -0
- package/bridges/discord-bot.js +431 -0
- package/bridges/farcaster.js +299 -0
- package/bridges/telegram.js +261 -0
- package/bridges/webhook-health.js +420 -0
- package/bridges/webhook-server.js +437 -0
- package/bridges/whatsapp.js +441 -0
- package/bridges/x-webhook.js +423 -0
- package/config.js +27 -15
- package/games/arcade.js +406 -0
- package/games/chess.js +451 -0
- package/games/colorguess.js +343 -0
- package/games/crossword-words.js +171 -0
- package/games/crossword.js +461 -0
- package/games/drawing.js +347 -0
- package/games/gameroulette.js +300 -0
- package/games/gamerouter.js +336 -0
- package/games/gamestatus.js +337 -0
- package/games/guessnumber.js +209 -0
- package/games/hangman.js +279 -0
- package/games/memory.js +338 -0
- package/games/multiplayer-tictactoe.js +389 -0
- package/games/pixelart.js +399 -0
- package/games/quickduel.js +354 -0
- package/games/riddle.js +371 -0
- package/games/rockpaperscissors.js +291 -0
- package/games/snake.js +406 -0
- package/games/storybuilder.js +343 -0
- package/games/tictactoe.js +345 -0
- package/games/twentyquestions.js +286 -0
- package/games/twotruths.js +207 -0
- package/games/werewolf.js +508 -0
- package/games/wordassociation.js +247 -0
- package/games/wordchain.js +135 -0
- package/index.js +116 -159
- package/intelligence/index.js +9 -2
- package/intelligence/interests.js +369 -0
- package/notification-emitter.js +77 -0
- package/notify.js +5 -1
- package/package.json +21 -16
- package/prompts.js +1 -1
- package/protocol/index.js +73 -0
- package/setup.js +480 -0
- package/smart-inbox.js +276 -0
- package/store/api.js +536 -215
- package/store/profiles.js +160 -12
- package/tools/_actions.js +362 -21
- package/tools/_discovery.js +119 -26
- package/tools/_shared/index.js +64 -0
- package/tools/_shared.js +234 -0
- package/tools/_work-context.js +338 -0
- package/tools/_work-context.manual-test.js +199 -0
- package/tools/_work-context.test.js +260 -0
- package/tools/activity.js +220 -0
- package/tools/analytics.js +191 -0
- package/tools/approve.js +197 -0
- package/tools/artifact-create.js +14 -3
- package/tools/artifacts-price.js +107 -0
- package/tools/available.js +120 -0
- package/tools/broadcast.js +325 -0
- package/tools/chat.js +202 -0
- package/tools/collaborative-drawing.js +1 -1
- package/tools/connection-status.js +178 -0
- package/tools/discover.js +350 -34
- package/tools/dm.js +80 -8
- package/tools/earnings.js +126 -0
- package/tools/feed.js +35 -4
- package/tools/follow.js +224 -0
- package/tools/friends.js +207 -0
- package/tools/gig-browse.js +206 -0
- package/tools/gig-complete.js +144 -0
- package/tools/health.js +87 -0
- package/tools/help.js +3 -3
- package/tools/idea.js +9 -2
- package/tools/inbox.js +289 -105
- package/tools/init.js +131 -34
- package/tools/invite.js +15 -4
- package/tools/leaderboard.js +117 -0
- package/tools/lib/git-apply.js +206 -0
- package/tools/lib/git-bundle.js +407 -0
- package/tools/migrate.js +3 -3
- package/tools/multiplayer-game.js +1 -1
- package/tools/onboarding.js +7 -7
- package/tools/open.js +143 -12
- package/tools/party-game.js +1 -1
- package/tools/plan.js +225 -0
- package/tools/proof-of-work.js +144 -0
- package/tools/reply.js +166 -0
- package/tools/report.js +1 -1
- package/tools/request.js +17 -3
- package/tools/schedule.js +367 -0
- package/tools/search-messages.js +123 -0
- package/tools/session.js +467 -0
- package/tools/session_price.js +128 -0
- package/tools/settings.js +90 -2
- package/tools/ship.js +30 -7
- package/tools/smart-check.js +201 -0
- package/tools/start.js +147 -12
- package/tools/status.js +53 -6
- package/tools/streak.js +147 -0
- package/tools/stuck.js +297 -0
- package/tools/subscribe.js +148 -0
- package/tools/subscriptions.js +134 -0
- package/tools/suggest-tags.js +6 -8
- package/tools/tag-suggestions.js +1 -1
- package/tools/tip.js +150 -77
- package/tools/token.js +4 -4
- package/tools/update.js +1 -1
- package/tools/wallet.js +221 -79
- package/tools/watch.js +157 -0
- package/tools/who.js +30 -1
- package/tools/withdraw.js +145 -0
- package/tools/work-summary.js +96 -0
- package/version.json +10 -8
- package/LICENSE +0 -21
- package/store/sqlite.js +0 -347
- /package/tools/{auto-suggest-connections.js → _deprecated/auto-suggest-connections.js} +0 -0
- /package/tools/{away.js → _deprecated/away.js} +0 -0
- /package/tools/{back.js → _deprecated/back.js} +0 -0
- /package/tools/{bootstrap-skills.js → _deprecated/bootstrap-skills.js} +0 -0
- /package/tools/{bridge-dashboard.js → _deprecated/bridge-dashboard.js} +0 -0
- /package/tools/{bridge-health.js → _deprecated/bridge-health.js} +0 -0
- /package/tools/{bridge-live.js → _deprecated/bridge-live.js} +0 -0
- /package/tools/{bridges.js → _deprecated/bridges.js} +0 -0
- /package/tools/{colorguess.js → _deprecated/colorguess.js} +0 -0
- /package/tools/{discover-insights.js → _deprecated/discover-insights.js} +0 -0
- /package/tools/{discover-momentum.js → _deprecated/discover-momentum.js} +0 -0
- /package/tools/{discovery-analytics.js → _deprecated/discovery-analytics.js} +0 -0
- /package/tools/{discovery-auto-suggest.js → _deprecated/discovery-auto-suggest.js} +0 -0
- /package/tools/{discovery-bootstrap.js → _deprecated/discovery-bootstrap.js} +0 -0
- /package/tools/{discovery-daily.js → _deprecated/discovery-daily.js} +0 -0
- /package/tools/{discovery-dashboard.js → _deprecated/discovery-dashboard.js} +0 -0
- /package/tools/{discovery-digest.js → _deprecated/discovery-digest.js} +0 -0
- /package/tools/{discovery-hub.js → _deprecated/discovery-hub.js} +0 -0
- /package/tools/{discovery-insights.js → _deprecated/discovery-insights.js} +0 -0
- /package/tools/{discovery-momentum.js → _deprecated/discovery-momentum.js} +0 -0
- /package/tools/{discovery-monitor.js → _deprecated/discovery-monitor.js} +0 -0
- /package/tools/{discovery-proactive.js → _deprecated/discovery-proactive.js} +0 -0
- /package/tools/{draw.js → _deprecated/draw.js} +0 -0
- /package/tools/{farcaster.js → _deprecated/farcaster.js} +0 -0
- /package/tools/{forget.js → _deprecated/forget.js} +0 -0
- /package/tools/{games-catalog.js → _deprecated/games-catalog.js} +0 -0
- /package/tools/{games.js → _deprecated/games.js} +0 -0
- /package/tools/{guessnumber.js → _deprecated/guessnumber.js} +0 -0
- /package/tools/{hangman.js → _deprecated/hangman.js} +0 -0
- /package/tools/{multiplayer-tictactoe.js → _deprecated/multiplayer-tictactoe.js} +0 -0
- /package/tools/{mute.js → _deprecated/mute.js} +0 -0
- /package/tools/{recall.js → _deprecated/recall.js} +0 -0
- /package/tools/{remember.js → _deprecated/remember.js} +0 -0
- /package/tools/{riddle.js → _deprecated/riddle.js} +0 -0
- /package/tools/{run-bootstrap.js → _deprecated/run-bootstrap.js} +0 -0
- /package/tools/{skills-analytics.js → _deprecated/skills-analytics.js} +0 -0
- /package/tools/{skills-bootstrap.js → _deprecated/skills-bootstrap.js} +0 -0
- /package/tools/{skills-dashboard.js → _deprecated/skills-dashboard.js} +0 -0
- /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
- /package/tools/{skills.js → _deprecated/skills.js} +0 -0
- /package/tools/{smart-intro.js → _deprecated/smart-intro.js} +0 -0
- /package/tools/{storybuilder.js → _deprecated/storybuilder.js} +0 -0
- /package/tools/{telegram-bot.js → _deprecated/telegram-bot.js} +0 -0
- /package/tools/{telegram-setup.js → _deprecated/telegram-setup.js} +0 -0
- /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
- /package/tools/{twentyquestions.js → _deprecated/twentyquestions.js} +0 -0
- /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe_earnings - View unified earnings dashboard
|
|
3
|
+
*
|
|
4
|
+
* Shows all revenue sources aggregated:
|
|
5
|
+
* - Artifact sales
|
|
6
|
+
* - Session PPV revenue
|
|
7
|
+
* - Subscription revenue
|
|
8
|
+
* - Gig completions
|
|
9
|
+
* - Tips received
|
|
10
|
+
* - On-chain balance
|
|
11
|
+
*
|
|
12
|
+
* Examples:
|
|
13
|
+
* - "vibe earnings"
|
|
14
|
+
* - "show my earnings"
|
|
15
|
+
* - "how much have I made"
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fetch = require('node-fetch');
|
|
19
|
+
const config = require('../config');
|
|
20
|
+
|
|
21
|
+
const definition = {
|
|
22
|
+
name: 'vibe_earnings',
|
|
23
|
+
description: 'View your unified earnings dashboard - artifact sales, session PPV, subscriptions, gigs, tips, and on-chain balance.',
|
|
24
|
+
inputSchema: {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
handle: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'Handle to check earnings for (defaults to your handle)'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
async function handler(args) {
|
|
36
|
+
const { handle } = args;
|
|
37
|
+
|
|
38
|
+
if (!config.isInitialized()) {
|
|
39
|
+
return {
|
|
40
|
+
display: 'Run `vibe init` first to set your identity.'
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const targetHandle = handle || config.getHandle();
|
|
45
|
+
const apiUrl = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
|
|
46
|
+
const token = config.getToken();
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const response = await fetch(
|
|
50
|
+
`${apiUrl}/api/earnings?handle=${encodeURIComponent(targetHandle)}`,
|
|
51
|
+
{
|
|
52
|
+
headers: token ? {
|
|
53
|
+
'Authorization': `Bearer ${token}`
|
|
54
|
+
} : {}
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const result = await response.json();
|
|
59
|
+
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
return {
|
|
62
|
+
display: `❌ ${result.error || 'Failed to fetch earnings'}`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { earnings, total_earned_display, pending_display, on_chain } = result;
|
|
67
|
+
|
|
68
|
+
let formatted = `
|
|
69
|
+
┌────────────────────────────────────────────────────────────┐
|
|
70
|
+
│ 💰 EARNINGS DASHBOARD │
|
|
71
|
+
│ @${targetHandle.padEnd(33)}│
|
|
72
|
+
├────────────────────────────────────────────────────────────┤
|
|
73
|
+
│ │
|
|
74
|
+
│ Total Earned: ${(total_earned_display || '$0').padEnd(40)} │
|
|
75
|
+
│ Pending (On-chain): ${(pending_display || '$0').padEnd(34)} │
|
|
76
|
+
│ │
|
|
77
|
+
├────────────────────────────────────────────────────────────┤
|
|
78
|
+
│ 📊 Breakdown by Source: │
|
|
79
|
+
│ │
|
|
80
|
+
│ Artifacts: ${(earnings.artifacts.revenue_display || '$0').padEnd(15)} (${earnings.artifacts.sales || 0} sales)${' '.repeat(17)} │
|
|
81
|
+
│ Sessions: ${(earnings.sessions.revenue_display || '$0').padEnd(15)} (${earnings.sessions.ppv_sales || 0} PPV)${' '.repeat(18)} │
|
|
82
|
+
│ Gigs: ${(earnings.gigs.revenue_display || '$0').padEnd(15)} (${earnings.gigs.completed || 0} completed)${' '.repeat(12)} │
|
|
83
|
+
│ Tips: ${(earnings.tips.revenue_display || '$0').padEnd(15)} (${earnings.tips.received || 0} received)${' '.repeat(13)} │
|
|
84
|
+
│ │
|
|
85
|
+
`.trim();
|
|
86
|
+
|
|
87
|
+
if (on_chain) {
|
|
88
|
+
formatted += `
|
|
89
|
+
├────────────────────────────────────────────────────────────┤
|
|
90
|
+
│ ⛓️ On-Chain Status: │`;
|
|
91
|
+
|
|
92
|
+
if (on_chain.registered) {
|
|
93
|
+
const walletDisplay = on_chain.wallet
|
|
94
|
+
? `${on_chain.wallet.slice(0, 10)}...${on_chain.wallet.slice(-8)}`
|
|
95
|
+
: 'not set';
|
|
96
|
+
formatted += `
|
|
97
|
+
│ │
|
|
98
|
+
│ Registered: Yes │
|
|
99
|
+
│ Wallet: ${walletDisplay.padEnd(47)} │
|
|
100
|
+
│ Balance: ${(on_chain.balance_eth + ' ETH').padEnd(46)} │`;
|
|
101
|
+
if (on_chain.can_withdraw) {
|
|
102
|
+
formatted += `
|
|
103
|
+
│ ✅ Ready to withdraw │`;
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
formatted += `
|
|
107
|
+
│ │
|
|
108
|
+
│ Registered: No │
|
|
109
|
+
│ ⚠️ Register to receive payments: vibe register_onchain │`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
formatted += `
|
|
114
|
+
│ │
|
|
115
|
+
└────────────────────────────────────────────────────────────┘`;
|
|
116
|
+
|
|
117
|
+
return { display: formatted, data: result };
|
|
118
|
+
|
|
119
|
+
} catch (error) {
|
|
120
|
+
return {
|
|
121
|
+
display: `❌ Failed to fetch earnings: ${error.message}`
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = { definition, handler };
|
package/tools/feed.js
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
const config = require('../config');
|
|
16
|
-
const { requireInit, header, emptyState, formatTimeAgo, divider } = require('./_shared');
|
|
16
|
+
const { requireInit, header, emptyState, formatTimeAgo, divider, fetchRelevantUsers } = require('./_shared');
|
|
17
|
+
const { actions, formatActions } = require('./_actions');
|
|
17
18
|
|
|
18
19
|
const definition = {
|
|
19
20
|
name: 'vibe_feed',
|
|
@@ -100,8 +101,27 @@ async function handler(args) {
|
|
|
100
101
|
);
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
//
|
|
104
|
-
|
|
104
|
+
// Fetch relevant users to personalize feed
|
|
105
|
+
// Content from relevant users gets a time boost (appears more recent)
|
|
106
|
+
const relevantHandles = new Set();
|
|
107
|
+
try {
|
|
108
|
+
const relevancy = await fetchRelevantUsers(myHandle, 'feed', 20);
|
|
109
|
+
if (relevancy && relevancy.matches) {
|
|
110
|
+
relevancy.matches.forEach(m => relevantHandles.add(m.handle.toLowerCase()));
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {
|
|
113
|
+
// Don't fail feed if relevancy fails
|
|
114
|
+
console.log('[feed] relevancy fetch error:', e.message);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Sort by timestamp with relevancy boost
|
|
118
|
+
// Relevant users get a 6-hour boost (their content appears "more recent")
|
|
119
|
+
const RELEVANCY_BOOST_MS = 6 * 60 * 60 * 1000; // 6 hours in ms
|
|
120
|
+
entries.sort((a, b) => {
|
|
121
|
+
const aBoost = relevantHandles.has(a.author.toLowerCase()) ? RELEVANCY_BOOST_MS : 0;
|
|
122
|
+
const bBoost = relevantHandles.has(b.author.toLowerCase()) ? RELEVANCY_BOOST_MS : 0;
|
|
123
|
+
return (b.timestamp + bBoost) - (a.timestamp + aBoost);
|
|
124
|
+
});
|
|
105
125
|
|
|
106
126
|
// Limit
|
|
107
127
|
entries = entries.slice(0, limit);
|
|
@@ -186,7 +206,18 @@ async function handler(args) {
|
|
|
186
206
|
display += ` _${timeAgo}_\n\n`;
|
|
187
207
|
});
|
|
188
208
|
|
|
189
|
-
|
|
209
|
+
// Build response with tip actions if we have ships
|
|
210
|
+
const response = { display };
|
|
211
|
+
|
|
212
|
+
// Find the most recent ship to offer tip action for
|
|
213
|
+
const recentShip = entries.find(e => e.category === 'shipped');
|
|
214
|
+
if (recentShip && recentShip.author.toLowerCase() !== myHandle.toLowerCase()) {
|
|
215
|
+
response.actions = formatActions(actions.afterShipView(recentShip));
|
|
216
|
+
response.hint = 'tip_ship_available';
|
|
217
|
+
response.ship_author = recentShip.author;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return response;
|
|
190
221
|
|
|
191
222
|
} catch (error) {
|
|
192
223
|
return { display: `⚠️ Failed to load feed: ${error.message}` };
|
package/tools/follow.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe follow — Follow/unfollow creators
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* - follow @handle → Follow a creator
|
|
6
|
+
* - unfollow @handle → Unfollow a creator
|
|
7
|
+
* - following → See who you follow (+ their live status)
|
|
8
|
+
* - followers → See who follows you
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const config = require('../config');
|
|
12
|
+
const { requireInit } = require('./_shared');
|
|
13
|
+
|
|
14
|
+
const definition = {
|
|
15
|
+
name: 'vibe_follow',
|
|
16
|
+
description: 'Follow creators to get notified when they go live. Unfollow, see who you follow, see your followers.',
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
action: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
enum: ['follow', 'unfollow', 'following', 'followers'],
|
|
23
|
+
description: 'Action: follow, unfollow, following, or followers'
|
|
24
|
+
},
|
|
25
|
+
handle: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Handle to follow/unfollow (e.g., @creator)'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
async function handler(args) {
|
|
34
|
+
const initCheck = requireInit();
|
|
35
|
+
if (initCheck) return initCheck;
|
|
36
|
+
|
|
37
|
+
const myHandle = config.getHandle();
|
|
38
|
+
const apiUrl = config.getApiUrl();
|
|
39
|
+
const action = args.action || 'following';
|
|
40
|
+
const targetHandle = args.handle?.replace('@', '').toLowerCase();
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// FOLLOW
|
|
44
|
+
if (action === 'follow') {
|
|
45
|
+
if (!targetHandle) {
|
|
46
|
+
return { display: '⚠️ Specify who to follow: `vibe follow @handle`' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const response = await fetch(`${apiUrl}/api/follow`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: { 'Content-Type': 'application/json' },
|
|
52
|
+
body: JSON.stringify({
|
|
53
|
+
follower: myHandle,
|
|
54
|
+
following: targetHandle
|
|
55
|
+
})
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const data = await response.json();
|
|
59
|
+
|
|
60
|
+
if (!data.success) {
|
|
61
|
+
return { display: `⚠️ ${data.error}` };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (data.action === 'followed') {
|
|
65
|
+
let display = `✅ Now following **@${targetHandle}**\n\n`;
|
|
66
|
+
display += `You'll be notified when they go live.\n\n`;
|
|
67
|
+
display += `_You follow ${data.counts.youAreFollowing} creators_`;
|
|
68
|
+
return { display };
|
|
69
|
+
} else {
|
|
70
|
+
return { display: `Already following @${targetHandle}` };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// UNFOLLOW
|
|
75
|
+
if (action === 'unfollow') {
|
|
76
|
+
if (!targetHandle) {
|
|
77
|
+
return { display: '⚠️ Specify who to unfollow: `vibe unfollow @handle`' };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const response = await fetch(`${apiUrl}/api/follow`, {
|
|
81
|
+
method: 'DELETE',
|
|
82
|
+
headers: { 'Content-Type': 'application/json' },
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
follower: myHandle,
|
|
85
|
+
following: targetHandle
|
|
86
|
+
})
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const data = await response.json();
|
|
90
|
+
|
|
91
|
+
if (!data.success) {
|
|
92
|
+
return { display: `⚠️ ${data.error}` };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let display = `✅ Unfollowed **@${targetHandle}**\n\n`;
|
|
96
|
+
display += `_You follow ${data.counts.youAreFollowing} creators_`;
|
|
97
|
+
return { display };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// FOLLOWING (who I follow)
|
|
101
|
+
if (action === 'following') {
|
|
102
|
+
const response = await fetch(`${apiUrl}/api/following?handle=${myHandle}`);
|
|
103
|
+
const data = await response.json();
|
|
104
|
+
|
|
105
|
+
if (!data.success) {
|
|
106
|
+
return { display: `⚠️ ${data.error}` };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (data.following.length === 0) {
|
|
110
|
+
let display = `## Following\n\n`;
|
|
111
|
+
display += `_You're not following anyone yet._\n\n`;
|
|
112
|
+
display += `**Discover creators:**\n`;
|
|
113
|
+
display += `- \`vibe discover\` — Find recommended creators\n`;
|
|
114
|
+
display += `- \`vibe follow @handle\` — Follow someone`;
|
|
115
|
+
return { display };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let display = `## Following (${data.count})\n\n`;
|
|
119
|
+
|
|
120
|
+
// Group by status
|
|
121
|
+
const live = data.following.filter(f => f.isLive);
|
|
122
|
+
const scheduled = data.following.filter(f => !f.isLive && f.nextScheduled);
|
|
123
|
+
const other = data.following.filter(f => !f.isLive && !f.nextScheduled);
|
|
124
|
+
|
|
125
|
+
if (live.length > 0) {
|
|
126
|
+
display += `**🔴 Live Now**\n`;
|
|
127
|
+
for (const f of live) {
|
|
128
|
+
display += `• **@${f.handle}** — [Watch](https://slashvibe.dev/watch/${f.roomId})\n`;
|
|
129
|
+
}
|
|
130
|
+
display += `\n`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (scheduled.length > 0) {
|
|
134
|
+
display += `**📅 Upcoming**\n`;
|
|
135
|
+
for (const f of scheduled) {
|
|
136
|
+
const when = formatScheduledTime(f.nextScheduled.scheduledFor);
|
|
137
|
+
display += `• **@${f.handle}** — ${f.nextScheduled.title} (${when})\n`;
|
|
138
|
+
}
|
|
139
|
+
display += `\n`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (other.length > 0) {
|
|
143
|
+
display += `**Following**\n`;
|
|
144
|
+
for (const f of other) {
|
|
145
|
+
const followers = f.followerCount > 0 ? ` (${f.followerCount} followers)` : '';
|
|
146
|
+
display += `• @${f.handle}${followers}\n`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { display };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// FOLLOWERS (who follows me)
|
|
154
|
+
if (action === 'followers') {
|
|
155
|
+
const response = await fetch(`${apiUrl}/api/followers?handle=${myHandle}`);
|
|
156
|
+
const data = await response.json();
|
|
157
|
+
|
|
158
|
+
if (!data.success) {
|
|
159
|
+
return { display: `⚠️ ${data.error}` };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (data.followers.length === 0) {
|
|
163
|
+
let display = `## Followers\n\n`;
|
|
164
|
+
display += `_No followers yet._\n\n`;
|
|
165
|
+
display += `**Build your audience:**\n`;
|
|
166
|
+
display += `- Go live: \`vibe broadcast start\`\n`;
|
|
167
|
+
display += `- Share what you're building\n`;
|
|
168
|
+
display += `- Engage with other creators`;
|
|
169
|
+
return { display };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let display = `## Followers (${data.count})\n\n`;
|
|
173
|
+
|
|
174
|
+
// Show active vs inactive
|
|
175
|
+
const active = data.followers.filter(f => f.isActive);
|
|
176
|
+
const inactive = data.followers.filter(f => !f.isActive);
|
|
177
|
+
|
|
178
|
+
if (active.length > 0) {
|
|
179
|
+
display += `**🟢 Active Now**\n`;
|
|
180
|
+
for (const f of active) {
|
|
181
|
+
const building = f.building ? ` — ${f.building}` : '';
|
|
182
|
+
display += `• @${f.handle}${building}\n`;
|
|
183
|
+
}
|
|
184
|
+
display += `\n`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (inactive.length > 0) {
|
|
188
|
+
display += `**Followers**\n`;
|
|
189
|
+
for (const f of inactive.slice(0, 20)) {
|
|
190
|
+
display += `• @${f.handle}\n`;
|
|
191
|
+
}
|
|
192
|
+
if (inactive.length > 20) {
|
|
193
|
+
display += `_...and ${inactive.length - 20} more_\n`;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return { display };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return { display: 'Unknown action. Use: follow, unfollow, following, followers' };
|
|
201
|
+
|
|
202
|
+
} catch (error) {
|
|
203
|
+
return { display: `## Error\n\n${error.message}` };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function formatScheduledTime(isoString) {
|
|
208
|
+
const date = new Date(isoString);
|
|
209
|
+
const now = new Date();
|
|
210
|
+
const diffMs = date - now;
|
|
211
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
212
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
213
|
+
|
|
214
|
+
if (diffDays === 0) {
|
|
215
|
+
if (diffHours <= 1) return 'in < 1 hour';
|
|
216
|
+
return `in ${diffHours} hours`;
|
|
217
|
+
} else if (diffDays === 1) {
|
|
218
|
+
return 'tomorrow';
|
|
219
|
+
} else {
|
|
220
|
+
return `in ${diffDays} days`;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
module.exports = { definition, handler };
|
package/tools/friends.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe friends — Find your GitHub friends on /vibe
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* - vibe friends → Find GitHub connections on vibe
|
|
6
|
+
* - vibe friends follow → Follow all matched friends
|
|
7
|
+
* - vibe friends follow @user → Follow specific friend
|
|
8
|
+
*
|
|
9
|
+
* This is the Plaxo move — leverage existing social graphs
|
|
10
|
+
* to bootstrap the /vibe network.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const config = require('../config');
|
|
14
|
+
const { requireInit } = require('./_shared');
|
|
15
|
+
|
|
16
|
+
const definition = {
|
|
17
|
+
name: 'vibe_friends',
|
|
18
|
+
description: 'Find your GitHub friends on /vibe and follow them. "5 of your GitHub friends are here — follow them?"',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
action: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
enum: ['find', 'follow'],
|
|
25
|
+
description: 'Action: find (list GitHub friends on vibe) or follow (follow them)'
|
|
26
|
+
},
|
|
27
|
+
handle: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'Specific handle to follow (optional, defaults to follow all)'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
async function handler(args) {
|
|
36
|
+
const initCheck = requireInit();
|
|
37
|
+
if (initCheck) return initCheck;
|
|
38
|
+
|
|
39
|
+
const myHandle = config.getHandle();
|
|
40
|
+
const apiUrl = config.getApiUrl();
|
|
41
|
+
const action = args.action || 'find';
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
// FIND — Discover GitHub friends on vibe
|
|
45
|
+
if (action === 'find') {
|
|
46
|
+
const response = await fetch(`${apiUrl}/api/github/friends?handle=${myHandle}`);
|
|
47
|
+
const data = await response.json();
|
|
48
|
+
|
|
49
|
+
if (!data.success) {
|
|
50
|
+
if (data.error?.includes('No GitHub connection')) {
|
|
51
|
+
return {
|
|
52
|
+
display: `## Connect Your GitHub
|
|
53
|
+
|
|
54
|
+
To find your GitHub friends on /vibe, you need to connect your GitHub account.
|
|
55
|
+
|
|
56
|
+
**Run:**
|
|
57
|
+
\`vibe init\`
|
|
58
|
+
|
|
59
|
+
This will authenticate with GitHub and unlock:
|
|
60
|
+
• Find friends who are already on /vibe
|
|
61
|
+
• One-click follow all
|
|
62
|
+
• Your GitHub username as your verified handle`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return { display: `⚠️ ${data.error}` };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { github, friends, stats, cta } = data;
|
|
69
|
+
|
|
70
|
+
// Track onboarding completion (searched for GitHub friends)
|
|
71
|
+
try {
|
|
72
|
+
await fetch(`${apiUrl}/api/onboarding/checklist`, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: { 'Content-Type': 'application/json' },
|
|
75
|
+
body: JSON.stringify({
|
|
76
|
+
handle: myHandle,
|
|
77
|
+
taskId: 'find_github_friends',
|
|
78
|
+
metadata: { friendsFound: friends.length }
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
} catch (e) {
|
|
82
|
+
// Non-critical, don't fail
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (friends.length === 0) {
|
|
86
|
+
let display = `## GitHub Friends on /vibe\n\n`;
|
|
87
|
+
display += `Connected as: **${github.login}** (${github.following} following, ${github.followers} followers)\n\n`;
|
|
88
|
+
display += `_None of your GitHub connections are on /vibe yet._\n\n`;
|
|
89
|
+
display += `**Be the connector!**\n`;
|
|
90
|
+
display += `Send your friends an invite: \`vibe invite\``;
|
|
91
|
+
return { display };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let display = `## 🎉 GitHub Friends on /vibe!\n\n`;
|
|
95
|
+
display += `Connected as: **${github.login}**\n`;
|
|
96
|
+
|
|
97
|
+
if (cta) {
|
|
98
|
+
display += `\n**${cta}**\n\n`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
display += `---\n\n`;
|
|
102
|
+
|
|
103
|
+
// Group by status
|
|
104
|
+
const toFollow = friends.filter(f => !f.alreadyFollowing);
|
|
105
|
+
const alreadyFollowing = friends.filter(f => f.alreadyFollowing);
|
|
106
|
+
|
|
107
|
+
if (toFollow.length > 0) {
|
|
108
|
+
display += `### Not Yet Following\n\n`;
|
|
109
|
+
for (const friend of toFollow.slice(0, 10)) {
|
|
110
|
+
const mutual = friend.isMutualOnGithub ? ' 🤝' : '';
|
|
111
|
+
const genesis = friend.genesis ? ' ✨' : '';
|
|
112
|
+
display += `**@${friend.vibeHandle}**${genesis}${mutual}`;
|
|
113
|
+
if (friend.oneLiner) {
|
|
114
|
+
display += ` — ${friend.oneLiner}`;
|
|
115
|
+
}
|
|
116
|
+
display += `\n`;
|
|
117
|
+
}
|
|
118
|
+
if (toFollow.length > 10) {
|
|
119
|
+
display += `_...and ${toFollow.length - 10} more_\n`;
|
|
120
|
+
}
|
|
121
|
+
display += `\n`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (alreadyFollowing.length > 0) {
|
|
125
|
+
display += `### Already Following ✓\n\n`;
|
|
126
|
+
for (const friend of alreadyFollowing.slice(0, 5)) {
|
|
127
|
+
display += `@${friend.vibeHandle}`;
|
|
128
|
+
if (friend.oneLiner) {
|
|
129
|
+
display += ` — ${friend.oneLiner}`;
|
|
130
|
+
}
|
|
131
|
+
display += `\n`;
|
|
132
|
+
}
|
|
133
|
+
if (alreadyFollowing.length > 5) {
|
|
134
|
+
display += `_...and ${alreadyFollowing.length - 5} more_\n`;
|
|
135
|
+
}
|
|
136
|
+
display += `\n`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
display += `---\n\n`;
|
|
140
|
+
display += `**Stats:** ${stats.total_matches} friends found · ${stats.mutuals} GitHub mutuals · ${stats.already_following} already following\n\n`;
|
|
141
|
+
|
|
142
|
+
if (toFollow.length > 0) {
|
|
143
|
+
display += `**Follow all ${toFollow.length} at once:**\n`;
|
|
144
|
+
display += `\`vibe friends follow\``;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { display };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// FOLLOW — Follow GitHub friends
|
|
151
|
+
if (action === 'follow') {
|
|
152
|
+
// If specific handle provided, follow just that one
|
|
153
|
+
const handles = args.handle ? [args.handle.replace('@', '')] : null;
|
|
154
|
+
|
|
155
|
+
const response = await fetch(`${apiUrl}/api/github/friends`, {
|
|
156
|
+
method: 'POST',
|
|
157
|
+
headers: { 'Content-Type': 'application/json' },
|
|
158
|
+
body: JSON.stringify({
|
|
159
|
+
handle: myHandle,
|
|
160
|
+
handles
|
|
161
|
+
})
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const data = await response.json();
|
|
165
|
+
|
|
166
|
+
if (!data.success) {
|
|
167
|
+
return { display: `⚠️ ${data.error}` };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (data.followed.length === 0) {
|
|
171
|
+
return {
|
|
172
|
+
display: `## All Caught Up! ✓
|
|
173
|
+
|
|
174
|
+
You're already following all your GitHub friends on /vibe.
|
|
175
|
+
|
|
176
|
+
**Grow the network:**
|
|
177
|
+
• \`vibe invite\` — Generate invite link for friends
|
|
178
|
+
• \`vibe discover\` — Find new people to follow`
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let display = `## 🎉 Followed ${data.count} GitHub Friend${data.count === 1 ? '' : 's'}!\n\n`;
|
|
183
|
+
|
|
184
|
+
for (const handle of data.followed.slice(0, 10)) {
|
|
185
|
+
display += `✓ @${handle}\n`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (data.followed.length > 10) {
|
|
189
|
+
display += `...and ${data.followed.length - 10} more\n`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
display += `\n---\n\n`;
|
|
193
|
+
display += `**${data.message}**\n\n`;
|
|
194
|
+
display += `They'll see you in their followers. Say hi!\n`;
|
|
195
|
+
display += `\`vibe dm @${data.followed[0]} "Hey! Saw you're on /vibe too 👋"\``;
|
|
196
|
+
|
|
197
|
+
return { display };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return { display: 'Unknown action. Use: find, follow' };
|
|
201
|
+
|
|
202
|
+
} catch (error) {
|
|
203
|
+
return { display: `## Error\n\n${error.message}` };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = { definition, handler };
|