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
package/tools/init.js
CHANGED
|
@@ -16,6 +16,7 @@ const path = require('path');
|
|
|
16
16
|
const config = require('../config');
|
|
17
17
|
const store = require('../store');
|
|
18
18
|
const discord = require('../discord');
|
|
19
|
+
const authStore = require('../auth-store');
|
|
19
20
|
|
|
20
21
|
const CALLBACK_PORT = 9876;
|
|
21
22
|
const API_BASE = 'https://www.slashvibe.dev';
|
|
@@ -143,15 +144,34 @@ function detectTechStack() {
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
/**
|
|
146
|
-
*
|
|
147
|
+
* Fetch GitHub friends who are on /vibe (non-blocking)
|
|
148
|
+
*/
|
|
149
|
+
async function fetchGitHubFriends(handle) {
|
|
150
|
+
try {
|
|
151
|
+
const response = await fetch(`${API_BASE}/api/github/contacts?handle=${encodeURIComponent(handle)}`);
|
|
152
|
+
if (!response.ok) return null;
|
|
153
|
+
const data = await response.json();
|
|
154
|
+
if (!data.success) return null;
|
|
155
|
+
return {
|
|
156
|
+
friendsOnVibe: data.people_you_know?.slice(0, 5) || [],
|
|
157
|
+
inviteSuggestions: data.invite_suggestions?.slice(0, 10) || [],
|
|
158
|
+
totalContacts: data.stats?.total_contacts || 0
|
|
159
|
+
};
|
|
160
|
+
} catch (e) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Send personalized welcome from @seth
|
|
167
|
+
* Returns the welcome message content so we can show it inline
|
|
147
168
|
*/
|
|
148
169
|
async function sendPersonalizedWelcome(handle, oneLiner) {
|
|
149
170
|
try {
|
|
150
171
|
const repoName = detectRepoName();
|
|
151
172
|
const techStack = detectTechStack();
|
|
152
173
|
|
|
153
|
-
|
|
154
|
-
fetch(`${API_BASE}/api/onboarding/personalized-welcome`, {
|
|
174
|
+
const response = await fetch(`${API_BASE}/api/onboarding/personalized-welcome`, {
|
|
155
175
|
method: 'POST',
|
|
156
176
|
headers: { 'Content-Type': 'application/json' },
|
|
157
177
|
body: JSON.stringify({
|
|
@@ -159,14 +179,20 @@ async function sendPersonalizedWelcome(handle, oneLiner) {
|
|
|
159
179
|
oneLiner,
|
|
160
180
|
repoName,
|
|
161
181
|
techStack,
|
|
162
|
-
githubProfile: null
|
|
182
|
+
githubProfile: null
|
|
163
183
|
})
|
|
164
|
-
}).catch(e => {
|
|
165
|
-
console.error('[vibe_init] Personalized welcome failed:', e.message);
|
|
166
184
|
});
|
|
185
|
+
|
|
186
|
+
if (!response.ok) {
|
|
187
|
+
console.error('[vibe_init] Welcome API error:', response.status);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const result = await response.json();
|
|
192
|
+
return result.success ? result : null;
|
|
167
193
|
} catch (e) {
|
|
168
|
-
|
|
169
|
-
|
|
194
|
+
console.error('[vibe_init] Personalized welcome failed:', e.message);
|
|
195
|
+
return null;
|
|
170
196
|
}
|
|
171
197
|
}
|
|
172
198
|
|
|
@@ -175,7 +201,7 @@ const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
|
|
|
175
201
|
const AUTH_TIMEOUT_MS = 120000; // 2 minutes
|
|
176
202
|
|
|
177
203
|
/**
|
|
178
|
-
* Send welcome message from @
|
|
204
|
+
* Send welcome message from @seth (founder)
|
|
179
205
|
*/
|
|
180
206
|
async function sendWelcomeMessage(handle, one_liner) {
|
|
181
207
|
try {
|
|
@@ -194,17 +220,17 @@ async function sendWelcomeMessage(handle, one_liner) {
|
|
|
194
220
|
|
|
195
221
|
const definition = {
|
|
196
222
|
name: 'vibe_init',
|
|
197
|
-
description: '
|
|
223
|
+
description: 'Join /vibe social network. Opens browser for GitHub login - NO INPUT NEEDED. Your GitHub username becomes your handle automatically. Just run this and complete the browser auth.',
|
|
198
224
|
inputSchema: {
|
|
199
225
|
type: 'object',
|
|
200
226
|
properties: {
|
|
201
227
|
handle: {
|
|
202
228
|
type: 'string',
|
|
203
|
-
description: '
|
|
229
|
+
description: 'RARELY NEEDED - Override your handle (defaults to GitHub username, which is usually what you want)'
|
|
204
230
|
},
|
|
205
231
|
one_liner: {
|
|
206
232
|
type: 'string',
|
|
207
|
-
description: 'What are you building?
|
|
233
|
+
description: 'OPTIONAL - What are you building? Can set this later.'
|
|
208
234
|
}
|
|
209
235
|
},
|
|
210
236
|
required: []
|
|
@@ -252,10 +278,15 @@ function waitForCallback(requestedHandle, one_liner) {
|
|
|
252
278
|
// Save the token and handle
|
|
253
279
|
const finalHandle = requestedHandle || callbackHandle;
|
|
254
280
|
|
|
255
|
-
// Save to config
|
|
256
|
-
config.
|
|
281
|
+
// Save to config (file persistence for restarts)
|
|
282
|
+
config.saveAuthToken(token);
|
|
257
283
|
config.setSessionIdentity(finalHandle, one_liner || '');
|
|
258
284
|
|
|
285
|
+
// PUSH to in-memory auth store (immediate propagation)
|
|
286
|
+
authStore.setToken(token);
|
|
287
|
+
authStore.setHandle(finalHandle);
|
|
288
|
+
authStore.setOneLiner(one_liner || '');
|
|
289
|
+
|
|
259
290
|
// Update shared config
|
|
260
291
|
const cfg = config.load();
|
|
261
292
|
cfg.handle = finalHandle;
|
|
@@ -274,8 +305,8 @@ function waitForCallback(requestedHandle, one_liner) {
|
|
|
274
305
|
// Post to Discord
|
|
275
306
|
discord.postJoin(finalHandle, one_liner);
|
|
276
307
|
|
|
277
|
-
//
|
|
278
|
-
|
|
308
|
+
// NOTE: Welcome message is sent in the main return path (awaited)
|
|
309
|
+
// to ensure it arrives before we show the unread count
|
|
279
310
|
|
|
280
311
|
// Send success response to browser - lightweight, no infinite animations
|
|
281
312
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
@@ -562,7 +593,7 @@ async function handler(args) {
|
|
|
562
593
|
}
|
|
563
594
|
|
|
564
595
|
// Check if already authenticated
|
|
565
|
-
if (config.
|
|
596
|
+
if (config.hasOAuth()) {
|
|
566
597
|
const existingHandle = config.getHandle();
|
|
567
598
|
if (existingHandle) {
|
|
568
599
|
return {
|
|
@@ -605,24 +636,63 @@ To check messages: \`vibe inbox\``
|
|
|
605
636
|
// Wait for callback (blocks until auth completes or times out)
|
|
606
637
|
const result = await waitForCallback(h, one_liner);
|
|
607
638
|
|
|
608
|
-
//
|
|
609
|
-
let
|
|
639
|
+
// Send personalized welcome and wait for it (2.5s timeout)
|
|
640
|
+
let welcomeResult = null;
|
|
610
641
|
try {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
642
|
+
welcomeResult = await Promise.race([
|
|
643
|
+
sendPersonalizedWelcome(result.handle, one_liner),
|
|
644
|
+
new Promise(resolve => setTimeout(() => resolve(null), 2500))
|
|
645
|
+
]);
|
|
646
|
+
} catch (e) {
|
|
647
|
+
console.error('[vibe_init] Welcome message failed:', e.message);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Check for unread messages (includes the welcome we just sent)
|
|
651
|
+
let unreadCount = 0;
|
|
652
|
+
try {
|
|
653
|
+
unreadCount = await store.getUnreadCount(result.handle);
|
|
654
|
+
} catch (e) {}
|
|
655
|
+
|
|
656
|
+
// Fetch GitHub friends (non-blocking, 3s timeout)
|
|
657
|
+
let friendsData = null;
|
|
658
|
+
try {
|
|
659
|
+
const friendsPromise = fetchGitHubFriends(result.handle);
|
|
660
|
+
friendsData = await Promise.race([
|
|
661
|
+
friendsPromise,
|
|
662
|
+
new Promise(resolve => setTimeout(() => resolve(null), 3000))
|
|
663
|
+
]);
|
|
615
664
|
} catch (e) {}
|
|
616
665
|
|
|
617
666
|
// Generate authenticated banner with handle + unread (3 lines only - won't collapse)
|
|
618
|
-
const authBanner = generateAuthBanner(result.handle,
|
|
667
|
+
const authBanner = generateAuthBanner(result.handle, unreadCount, onlineCount);
|
|
668
|
+
|
|
669
|
+
// Build friends section if we have data
|
|
670
|
+
let friendsSection = '';
|
|
671
|
+
if (friendsData?.friendsOnVibe?.length > 0) {
|
|
672
|
+
const friendHandles = friendsData.friendsOnVibe.map(f => `@${f.vibe_handle}`).join(', ');
|
|
673
|
+
friendsSection = `\n\n🤝 **${friendsData.friendsOnVibe.length} of your GitHub friends are here!**\n${friendHandles}`;
|
|
674
|
+
} else if (friendsData?.totalContacts > 0) {
|
|
675
|
+
friendsSection = `\n\n👋 None of your GitHub friends are on /vibe yet — say **"vibe invite"** to bring them in!`;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Build welcome section - show inline if we have the message
|
|
679
|
+
let welcomeSection = '';
|
|
680
|
+
if (welcomeResult?.messageText) {
|
|
681
|
+
welcomeSection = `\n\n---\n**📨 Welcome message from @seth:**\n\n> ${welcomeResult.messageText.split('\n').join('\n> ')}\n\n_Reply with **"vibe dm @seth [message]"** to say hi!_`;
|
|
682
|
+
} else {
|
|
683
|
+
welcomeSection = '\n\n---\n**📨 @seth sent you a welcome message!**\n\nSay **"vibe inbox"** to read it and get started.';
|
|
684
|
+
}
|
|
619
685
|
|
|
620
686
|
return {
|
|
621
|
-
display: authBanner,
|
|
687
|
+
display: authBanner + friendsSection + welcomeSection,
|
|
622
688
|
onboarding: {
|
|
623
689
|
isNewUser: true,
|
|
624
690
|
handle: result.handle,
|
|
625
|
-
hint: 'show_onboarding_options'
|
|
691
|
+
hint: 'show_onboarding_options',
|
|
692
|
+
hasWelcomeMessage: !!welcomeResult,
|
|
693
|
+
welcomeText: welcomeResult?.messageText || null,
|
|
694
|
+
friendsOnVibe: friendsData?.friendsOnVibe || [],
|
|
695
|
+
inviteSuggestions: friendsData?.inviteSuggestions || []
|
|
626
696
|
}
|
|
627
697
|
};
|
|
628
698
|
|
|
@@ -631,7 +701,15 @@ To check messages: \`vibe inbox\``
|
|
|
631
701
|
return {
|
|
632
702
|
display: `## Auth already in progress
|
|
633
703
|
|
|
634
|
-
Another login
|
|
704
|
+
Another login is running. Either:
|
|
705
|
+
1. **Complete it** in your browser, or
|
|
706
|
+
2. **Wait 30 seconds** and try again
|
|
707
|
+
|
|
708
|
+
If stuck, kill the auth server:
|
|
709
|
+
\`\`\`
|
|
710
|
+
lsof -ti:9876 | xargs kill
|
|
711
|
+
\`\`\`
|
|
712
|
+
Then run \`vibe init\` again.`
|
|
635
713
|
};
|
|
636
714
|
}
|
|
637
715
|
|
|
@@ -639,16 +717,26 @@ Another login flow is running. Complete it in your browser or wait a moment and
|
|
|
639
717
|
return {
|
|
640
718
|
display: `## Auth timed out
|
|
641
719
|
|
|
642
|
-
The login
|
|
720
|
+
The browser login wasn't completed within 2 minutes.
|
|
721
|
+
|
|
722
|
+
**Try again:**
|
|
723
|
+
Just say "add the vibe mcp server" and complete the browser auth faster this time.
|
|
724
|
+
|
|
725
|
+
**Tip:** Keep Claude Code visible so you can see when auth completes.`
|
|
643
726
|
};
|
|
644
727
|
}
|
|
645
728
|
|
|
646
729
|
return {
|
|
647
730
|
display: `## Failed to authenticate
|
|
648
731
|
|
|
649
|
-
Error
|
|
732
|
+
**Error:** ${err.message}
|
|
733
|
+
|
|
734
|
+
**Quick fixes:**
|
|
735
|
+
1. Check your internet connection
|
|
736
|
+
2. Try again: just say "vibe init"
|
|
737
|
+
3. Check status: slashvibe.dev/status
|
|
650
738
|
|
|
651
|
-
|
|
739
|
+
**Need help?** DM @seth on Twitter or email seth@slashvibe.dev`
|
|
652
740
|
};
|
|
653
741
|
}
|
|
654
742
|
}
|
|
@@ -700,7 +788,16 @@ Local config saved. Heartbeats will use username fallback.`
|
|
|
700
788
|
discord.postJoin(h, one_liner);
|
|
701
789
|
|
|
702
790
|
// Send personalized welcome from @vibe (non-blocking)
|
|
703
|
-
|
|
791
|
+
// Send personalized welcome and wait for it
|
|
792
|
+
let welcomeResult = null;
|
|
793
|
+
try {
|
|
794
|
+
welcomeResult = await Promise.race([
|
|
795
|
+
sendPersonalizedWelcome(h, one_liner),
|
|
796
|
+
new Promise(resolve => setTimeout(() => resolve(null), 2500))
|
|
797
|
+
]);
|
|
798
|
+
} catch (e) {
|
|
799
|
+
console.error('[vibe_init] Welcome message failed:', e.message);
|
|
800
|
+
}
|
|
704
801
|
|
|
705
802
|
// Check for unread messages
|
|
706
803
|
let unreadNotice = '';
|
|
@@ -718,14 +815,14 @@ Local config saved. Heartbeats will use username fallback.`
|
|
|
718
815
|
**@${h}**
|
|
719
816
|
_${one_liner}_${unreadNotice}${keypairNote}
|
|
720
817
|
|
|
721
|
-
📨 **Check your messages** — @
|
|
818
|
+
📨 **Check your messages** — @seth sent you a personalized welcome!
|
|
722
819
|
|
|
723
820
|
⚠️ **Using local keys** — consider upgrading to GitHub auth:
|
|
724
821
|
\`vibe init\` — Sign in with GitHub for verified identity
|
|
725
822
|
|
|
726
823
|
### Onboarding Checklist
|
|
727
|
-
[ ] Read your welcome message from @
|
|
728
|
-
[ ] Reply to @
|
|
824
|
+
[ ] Read your welcome message from @seth
|
|
825
|
+
[ ] Reply to @seth
|
|
729
826
|
[ ] Message one recommended builder
|
|
730
827
|
[ ] Post your first ship
|
|
731
828
|
[ ] Leave some feedback
|
package/tools/invite.js
CHANGED
|
@@ -203,14 +203,25 @@ _Copy the link above and send it._`
|
|
|
203
203
|
const genNote = justGenerated ? ' (just generated)' : '';
|
|
204
204
|
const randomMsg = INVITE_MESSAGES[Math.floor(Math.random() * INVITE_MESSAGES.length)];
|
|
205
205
|
|
|
206
|
+
// Build social share URLs
|
|
207
|
+
const tweetText = encodeURIComponent(`${randomMsg}\n\nhttps://${shareUrl}`);
|
|
208
|
+
const twitterUrl = `https://twitter.com/intent/tweet?text=${tweetText}`;
|
|
209
|
+
|
|
206
210
|
return {
|
|
207
211
|
display: `## Quick Invite${genNote}
|
|
208
212
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
213
|
+
**Copy this:**
|
|
214
|
+
\`\`\`
|
|
215
|
+
${randomMsg}
|
|
216
|
+
|
|
217
|
+
https://${shareUrl}
|
|
218
|
+
\`\`\`
|
|
219
|
+
|
|
220
|
+
**Or share directly:**
|
|
221
|
+
- [Tweet it](${twitterUrl})
|
|
222
|
+
- Slack/Discord: Just paste the link above
|
|
212
223
|
|
|
213
|
-
|
|
224
|
+
${shareCode ? `_When they join, you both get bonus codes._` : ''}`
|
|
214
225
|
};
|
|
215
226
|
}
|
|
216
227
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe_leaderboard — See top builders on /vibe
|
|
3
|
+
*
|
|
4
|
+
* Shows the leaderboard ranked by Vibe Score:
|
|
5
|
+
* - Ships posted (building stuff)
|
|
6
|
+
* - Reactions received (quality signal)
|
|
7
|
+
* - Comments given (helping others)
|
|
8
|
+
* - Streak consistency (showing up)
|
|
9
|
+
* - Invites redeemed (growing community)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const config = require('../config');
|
|
13
|
+
|
|
14
|
+
const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
|
|
15
|
+
|
|
16
|
+
const definition = {
|
|
17
|
+
name: 'vibe_leaderboard',
|
|
18
|
+
description: 'See top builders on /vibe ranked by Vibe Score',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
category: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
enum: ['overall', 'ships', 'streaks', 'reactions', 'helpful'],
|
|
25
|
+
description: 'Leaderboard category (default: overall)'
|
|
26
|
+
},
|
|
27
|
+
limit: {
|
|
28
|
+
type: 'number',
|
|
29
|
+
description: 'Number of entries to show (default: 10, max: 50)'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
async function handler(args) {
|
|
36
|
+
const { category = 'overall', limit = 10 } = args;
|
|
37
|
+
const maxLimit = Math.min(limit, 50);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const endpoint = `${API_URL}/api/growth/leaderboard?category=${category}&limit=${maxLimit}`;
|
|
41
|
+
const response = await fetch(endpoint);
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
|
|
44
|
+
if (!data.success) {
|
|
45
|
+
return { display: `\u274c Failed to load leaderboard: ${data.error}` };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const myHandle = config.isInitialized() ? config.getHandle() : null;
|
|
49
|
+
|
|
50
|
+
let display = `## /vibe Leaderboard\n`;
|
|
51
|
+
display += `**Category:** ${category} | **Showing:** Top ${maxLimit}\n\n`;
|
|
52
|
+
|
|
53
|
+
if (data.leaderboard.length === 0) {
|
|
54
|
+
display += `_No entries yet. Be the first to ship!_\n`;
|
|
55
|
+
return { display };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Table header
|
|
59
|
+
display += `| Rank | Builder | Vibe Score | Ships | Streak |\n`;
|
|
60
|
+
display += `|------|---------|------------|-------|--------|\n`;
|
|
61
|
+
|
|
62
|
+
for (const entry of data.leaderboard) {
|
|
63
|
+
const isMe = myHandle && entry.handle.toLowerCase() === myHandle.toLowerCase();
|
|
64
|
+
const highlight = isMe ? '**' : '';
|
|
65
|
+
|
|
66
|
+
// Position change indicator
|
|
67
|
+
let posIndicator = '';
|
|
68
|
+
if (entry.positionChange > 0) {
|
|
69
|
+
posIndicator = ` \u2191${entry.positionChange}`;
|
|
70
|
+
} else if (entry.positionChange < 0) {
|
|
71
|
+
posIndicator = ` \u2193${Math.abs(entry.positionChange)}`;
|
|
72
|
+
} else if (entry.isNew) {
|
|
73
|
+
posIndicator = ' \u2728';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Tier emoji
|
|
77
|
+
const tierEmoji = entry.tierDisplay?.emoji || '';
|
|
78
|
+
|
|
79
|
+
display += `| ${highlight}#${entry.rank}${posIndicator}${highlight} | ${highlight}@${entry.handle}${highlight} ${tierEmoji} | ${entry.vibeScore} | ${entry.stats?.ships || 0} | ${entry.streak || 0}d |\n`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Stats summary
|
|
83
|
+
if (data.stats) {
|
|
84
|
+
display += `\n### Community Stats\n`;
|
|
85
|
+
display += `- **Total Builders:** ${data.stats.total}\n`;
|
|
86
|
+
display += `- **Ships Posted:** ${data.stats.shipsCount}\n`;
|
|
87
|
+
display += `- **Active Today:** ${data.stats.activeToday}\n`;
|
|
88
|
+
display += `- **Avg Vibe Score:** ${data.stats.averageVibeScore}\n`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Highlights
|
|
92
|
+
if (data.highlights?.movers?.length > 0) {
|
|
93
|
+
display += `\n### \ud83d\ude80 Biggest Movers\n`;
|
|
94
|
+
for (const mover of data.highlights.movers.slice(0, 3)) {
|
|
95
|
+
display += `- @${mover.handle} +${mover.change} (now #${mover.rank})\n`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (data.highlights?.newEntries?.length > 0) {
|
|
100
|
+
display += `\n### \u2728 New Entries\n`;
|
|
101
|
+
for (const entry of data.highlights.newEntries.slice(0, 3)) {
|
|
102
|
+
display += `- @${entry.handle} joined at #${entry.rank}\n`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
display += `\n_${data.note}_`;
|
|
107
|
+
|
|
108
|
+
return { display };
|
|
109
|
+
|
|
110
|
+
} catch (e) {
|
|
111
|
+
return {
|
|
112
|
+
display: `## /vibe Leaderboard\n\n\u274c **Failed to load**\n\nError: ${e.message}`
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = { definition, handler };
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Apply Library
|
|
3
|
+
*
|
|
4
|
+
* Downloads and applies git bundles from session forks.
|
|
5
|
+
* Used by session.js fork command to get actual code.
|
|
6
|
+
*
|
|
7
|
+
* Key functions:
|
|
8
|
+
* - downloadBundle(url) - Download bundle from signed URL
|
|
9
|
+
* - applyBundleFromUrl(url, branchName) - Download and apply in one step
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { execSync } = require('child_process');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const https = require('https');
|
|
17
|
+
const http = require('http');
|
|
18
|
+
|
|
19
|
+
const { isGitRepo, validateBundle, applyBundle } = require('./git-bundle');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Download a bundle from a URL
|
|
23
|
+
* @param {string} url - The signed download URL
|
|
24
|
+
* @returns {Promise<object>} - { success, buffer, error? }
|
|
25
|
+
*/
|
|
26
|
+
async function downloadBundle(url) {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
const protocol = url.startsWith('https') ? https : http;
|
|
29
|
+
|
|
30
|
+
const request = protocol.get(url, (response) => {
|
|
31
|
+
// Handle redirects
|
|
32
|
+
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
33
|
+
downloadBundle(response.headers.location).then(resolve);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (response.statusCode !== 200) {
|
|
38
|
+
resolve({
|
|
39
|
+
success: false,
|
|
40
|
+
error: `HTTP ${response.statusCode}: ${response.statusMessage}`,
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const chunks = [];
|
|
46
|
+
response.on('data', (chunk) => chunks.push(chunk));
|
|
47
|
+
response.on('end', () => {
|
|
48
|
+
const buffer = Buffer.concat(chunks);
|
|
49
|
+
resolve({
|
|
50
|
+
success: true,
|
|
51
|
+
buffer,
|
|
52
|
+
size: buffer.length,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
response.on('error', (err) => {
|
|
56
|
+
resolve({ success: false, error: err.message });
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
request.on('error', (err) => {
|
|
61
|
+
resolve({ success: false, error: err.message });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Timeout after 60 seconds
|
|
65
|
+
request.setTimeout(60000, () => {
|
|
66
|
+
request.destroy();
|
|
67
|
+
resolve({ success: false, error: 'Download timeout (60s)' });
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Download and apply a bundle from a URL
|
|
74
|
+
* @param {string} url - The signed download URL
|
|
75
|
+
* @param {string} branchName - Name for the new branch (default: 'forked-session')
|
|
76
|
+
* @returns {Promise<object>} - { success, branch, commits?, error? }
|
|
77
|
+
*/
|
|
78
|
+
async function applyBundleFromUrl(url, branchName = 'forked-session') {
|
|
79
|
+
// Check we're in a git repo
|
|
80
|
+
if (!isGitRepo()) {
|
|
81
|
+
return { success: false, error: 'Not in a git repository' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Download the bundle
|
|
85
|
+
console.log(`[git-apply] Downloading bundle...`);
|
|
86
|
+
const downloadResult = await downloadBundle(url);
|
|
87
|
+
|
|
88
|
+
if (!downloadResult.success) {
|
|
89
|
+
return { success: false, error: `Download failed: ${downloadResult.error}` };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(`[git-apply] Downloaded ${downloadResult.size} bytes`);
|
|
93
|
+
|
|
94
|
+
// Validate the bundle
|
|
95
|
+
const validation = validateBundle(downloadResult.buffer);
|
|
96
|
+
if (!validation.success) {
|
|
97
|
+
return { success: false, error: `Invalid bundle: ${validation.error}` };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Apply the bundle
|
|
101
|
+
console.log(`[git-apply] Applying bundle to branch: ${branchName}`);
|
|
102
|
+
const applyResult = applyBundle(downloadResult.buffer, branchName);
|
|
103
|
+
|
|
104
|
+
if (!applyResult.success) {
|
|
105
|
+
return { success: false, error: `Apply failed: ${applyResult.error}` };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
success: true,
|
|
110
|
+
branch: applyResult.branch,
|
|
111
|
+
refs: applyResult.refs,
|
|
112
|
+
size: downloadResult.size,
|
|
113
|
+
message: `Bundle applied to branch '${applyResult.branch}'. Use 'git checkout ${applyResult.branch}' to view.`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check out the forked branch
|
|
119
|
+
* @param {string} branchName - Branch to checkout
|
|
120
|
+
* @returns {object} - { success, error? }
|
|
121
|
+
*/
|
|
122
|
+
function checkoutBranch(branchName) {
|
|
123
|
+
try {
|
|
124
|
+
execSync(`git checkout ${branchName}`, {
|
|
125
|
+
stdio: 'pipe',
|
|
126
|
+
encoding: 'utf8',
|
|
127
|
+
});
|
|
128
|
+
return { success: true };
|
|
129
|
+
} catch (error) {
|
|
130
|
+
return { success: false, error: error.message };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get info about a branch
|
|
136
|
+
* @param {string} branchName - Branch name
|
|
137
|
+
* @returns {object} - { exists, current, commits? }
|
|
138
|
+
*/
|
|
139
|
+
function getBranchInfo(branchName) {
|
|
140
|
+
try {
|
|
141
|
+
// Check if branch exists
|
|
142
|
+
const branches = execSync('git branch --list', {
|
|
143
|
+
stdio: 'pipe',
|
|
144
|
+
encoding: 'utf8',
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const branchList = branches.split('\n').map((b) => b.replace(/^\*?\s*/, '').trim());
|
|
148
|
+
const exists = branchList.includes(branchName);
|
|
149
|
+
|
|
150
|
+
// Check current branch
|
|
151
|
+
const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
152
|
+
stdio: 'pipe',
|
|
153
|
+
encoding: 'utf8',
|
|
154
|
+
}).trim();
|
|
155
|
+
|
|
156
|
+
const result = {
|
|
157
|
+
exists,
|
|
158
|
+
current: currentBranch === branchName,
|
|
159
|
+
currentBranch,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Get commit count if branch exists
|
|
163
|
+
if (exists) {
|
|
164
|
+
try {
|
|
165
|
+
const commitCount = execSync(`git rev-list --count ${branchName}`, {
|
|
166
|
+
stdio: 'pipe',
|
|
167
|
+
encoding: 'utf8',
|
|
168
|
+
});
|
|
169
|
+
result.commits = parseInt(commitCount.trim(), 10);
|
|
170
|
+
} catch (e) {
|
|
171
|
+
// Branch might not have commits yet
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return result;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
return { exists: false, current: false, error: error.message };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Delete a branch
|
|
183
|
+
* @param {string} branchName - Branch to delete
|
|
184
|
+
* @param {boolean} force - Force delete even if not merged
|
|
185
|
+
* @returns {object} - { success, error? }
|
|
186
|
+
*/
|
|
187
|
+
function deleteBranch(branchName, force = false) {
|
|
188
|
+
try {
|
|
189
|
+
const flag = force ? '-D' : '-d';
|
|
190
|
+
execSync(`git branch ${flag} ${branchName}`, {
|
|
191
|
+
stdio: 'pipe',
|
|
192
|
+
encoding: 'utf8',
|
|
193
|
+
});
|
|
194
|
+
return { success: true };
|
|
195
|
+
} catch (error) {
|
|
196
|
+
return { success: false, error: error.message };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports = {
|
|
201
|
+
downloadBundle,
|
|
202
|
+
applyBundleFromUrl,
|
|
203
|
+
checkoutBranch,
|
|
204
|
+
getBranchInfo,
|
|
205
|
+
deleteBranch,
|
|
206
|
+
};
|