slashvibe-mcp 0.2.9 ā 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- 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 +154 -5
- package/crypto.js +3 -8
- 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 +105 -216
- package/intelligence/index.js +45 -0
- package/intelligence/infer.js +316 -0
- package/intelligence/interests.js +369 -0
- package/intelligence/patterns.js +651 -0
- package/intelligence/proactive.js +358 -0
- package/intelligence/serendipity.js +306 -0
- package/memory.js +166 -0
- package/notification-emitter.js +77 -0
- package/notify.js +102 -1
- package/package.json +21 -7
- package/prompts.js +1 -1
- package/protocol/index.js +161 -1
- package/setup.js +402 -0
- package/store/api.js +528 -82
- package/store/profiles.js +160 -12
- package/tools/_actions.js +463 -16
- package/tools/_deprecated/auto-suggest-connections.js +304 -0
- package/tools/_deprecated/bootstrap-skills.js +231 -0
- package/tools/_deprecated/bridge-dashboard.js +342 -0
- package/tools/_deprecated/bridge-health.js +400 -0
- package/tools/_deprecated/bridge-live.js +384 -0
- package/tools/_deprecated/bridges.js +383 -0
- package/tools/_deprecated/colorguess.js +281 -0
- package/tools/_deprecated/discover-insights.js +379 -0
- package/tools/_deprecated/discover-momentum.js +256 -0
- package/tools/_deprecated/discovery-analytics.js +345 -0
- package/tools/_deprecated/discovery-auto-suggest.js +275 -0
- package/tools/_deprecated/discovery-bootstrap.js +267 -0
- package/tools/_deprecated/discovery-daily.js +375 -0
- package/tools/_deprecated/discovery-dashboard.js +385 -0
- package/tools/_deprecated/discovery-digest.js +314 -0
- package/tools/_deprecated/discovery-hub.js +357 -0
- package/tools/_deprecated/discovery-insights.js +384 -0
- package/tools/_deprecated/discovery-momentum.js +281 -0
- package/tools/_deprecated/discovery-monitor.js +319 -0
- package/tools/_deprecated/discovery-proactive.js +300 -0
- package/tools/_deprecated/draw.js +317 -0
- package/tools/_deprecated/farcaster.js +307 -0
- package/tools/_deprecated/forget.js +119 -0
- package/tools/_deprecated/games-catalog.js +376 -0
- package/tools/_deprecated/games.js +313 -0
- package/tools/_deprecated/guessnumber.js +194 -0
- package/tools/_deprecated/hangman.js +129 -0
- package/tools/_deprecated/multiplayer-tictactoe.js +303 -0
- package/tools/_deprecated/recall.js +147 -0
- package/tools/_deprecated/remember.js +86 -0
- package/tools/_deprecated/riddle.js +240 -0
- package/tools/_deprecated/run-bootstrap.js +69 -0
- package/tools/_deprecated/skills-analytics.js +349 -0
- package/tools/_deprecated/skills-bootstrap.js +301 -0
- package/tools/_deprecated/skills-dashboard.js +268 -0
- package/tools/_deprecated/skills.js +380 -0
- package/tools/_deprecated/smart-intro.js +353 -0
- package/tools/_deprecated/storybuilder.js +331 -0
- package/tools/_deprecated/telegram-bot.js +183 -0
- package/tools/_deprecated/telegram-setup.js +214 -0
- package/tools/_deprecated/twentyquestions.js +143 -0
- package/tools/_discovery-enhanced.js +290 -0
- package/tools/_discovery.js +439 -0
- package/tools/_proactive-discovery.js +301 -0
- 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/admin-inbox.js +218 -0
- package/tools/agent-treasury.js +288 -0
- package/tools/analytics.js +191 -0
- package/tools/approve.js +197 -0
- package/tools/arcade.js +173 -0
- package/tools/artifact-create.js +17 -11
- package/tools/artifact-view.js +31 -1
- package/tools/artifacts-price.js +47 -43
- package/tools/ask-expert.js +160 -0
- package/tools/available.js +120 -0
- package/tools/become-expert.js +150 -0
- package/tools/broadcast.js +286 -0
- package/tools/chat.js +202 -0
- package/tools/collaborative-drawing.js +286 -0
- package/tools/connection-status.js +178 -0
- package/tools/crossword.js +17 -1
- package/tools/discover.js +350 -34
- package/tools/dm.js +115 -73
- package/tools/drawing.js +1 -1
- package/tools/earnings.js +126 -0
- package/tools/echo.js +16 -0
- package/tools/feed.js +51 -7
- package/tools/follow.js +224 -0
- package/tools/friends.js +207 -0
- package/tools/game.js +2 -2
- package/tools/genesis.js +233 -0
- package/tools/gig-browse.js +206 -0
- package/tools/gig-complete.js +144 -0
- package/tools/handoff.js +7 -1
- package/tools/help.js +4 -4
- package/tools/idea.js +9 -2
- package/tools/inbox.js +334 -28
- package/tools/init.js +727 -36
- package/tools/invite.js +15 -4
- package/tools/l2-bridge.js +272 -0
- package/tools/l2-status.js +217 -0
- package/tools/l2.js +206 -0
- package/tools/migrate.js +156 -0
- package/tools/mint.js +377 -0
- package/tools/multiplayer-game.js +1 -1
- package/tools/notifications.js +415 -0
- package/tools/observe.js +200 -0
- package/tools/onboarding.js +147 -0
- package/tools/open.js +143 -12
- package/tools/party-game.js +2 -2
- package/tools/plan.js +225 -0
- package/tools/presence-agent.js +7 -0
- package/tools/proof-of-work.js +100 -104
- package/tools/pulse.js +218 -0
- package/tools/reply.js +166 -0
- package/tools/report.js +2 -2
- package/tools/reputation.js +175 -0
- package/tools/request.js +17 -3
- package/tools/schedule.js +367 -0
- package/tools/search-messages.js +123 -0
- package/tools/session.js +420 -0
- package/tools/session_price.js +128 -0
- package/tools/settings.js +126 -3
- package/tools/ship.js +31 -8
- package/tools/shipback.js +326 -0
- package/tools/smart-check.js +201 -0
- package/tools/social-processor.js +445 -0
- package/tools/solo-game.js +1 -1
- package/tools/start.js +335 -93
- package/tools/status.js +53 -6
- 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 +257 -0
- package/tools/tip.js +193 -0
- package/tools/token.js +103 -0
- package/tools/update.js +1 -1
- package/tools/wallet.js +239 -186
- package/tools/watch.js +157 -0
- package/tools/webhook-test.js +388 -0
- package/tools/who.js +54 -3
- package/tools/withdraw.js +145 -0
- package/tools/work-summary.js +96 -0
- package/tools/workshop.js +327 -0
- package/tools/x-mentions.js +1 -1
- package/version.json +14 -3
- package/tools/artifacts-buy.js +0 -111
- package/tools/connect.js +0 -284
- package/tools/gigs-apply.js +0 -99
- package/tools/gigs-browse.js +0 -114
- package/tools/gigs-complete.js +0 -128
- package/tools/gigs-post.js +0 -140
- package/tools/live-off.js +0 -109
- package/tools/live-watch.js +0 -176
- package/tools/live.js +0 -128
- package/tools/sessions-browse.js +0 -105
- package/tools/whats-happening.js +0 -125
- /package/tools/{away.js ā _deprecated/away.js} +0 -0
- /package/tools/{back.js ā _deprecated/back.js} +0 -0
- /package/tools/{mute.js ā _deprecated/mute.js} +0 -0
- /package/tools/{skills-exchange.js ā _deprecated/skills-exchange.js} +0 -0
- /package/tools/{tictactoe.js ā _deprecated/tictactoe.js} +0 -0
- /package/tools/{wordassociation.js ā _deprecated/wordassociation.js} +0 -0
package/tools/init.js
CHANGED
|
@@ -1,79 +1,756 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* vibe init ā Set your identity
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Smooth browser-based OAuth flow:
|
|
5
|
+
* 1. Start local callback server on localhost:9876
|
|
6
|
+
* 2. Open browser to login page
|
|
7
|
+
* 3. User authenticates with GitHub/X
|
|
8
|
+
* 4. Browser redirects back to localhost with token
|
|
9
|
+
* 5. Tool WAITS for callback and returns success
|
|
5
10
|
*/
|
|
6
11
|
|
|
12
|
+
const http = require('http');
|
|
13
|
+
const { exec, execSync } = require('child_process');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
7
16
|
const config = require('../config');
|
|
8
17
|
const store = require('../store');
|
|
9
|
-
const crypto = require('../crypto');
|
|
10
18
|
const discord = require('../discord');
|
|
19
|
+
const authStore = require('../auth-store');
|
|
20
|
+
|
|
21
|
+
const CALLBACK_PORT = 9876;
|
|
22
|
+
const API_BASE = 'https://www.slashvibe.dev';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Fetch online count from presence API
|
|
26
|
+
*/
|
|
27
|
+
async function getOnlineCount() {
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(`${API_BASE}/api/presence`);
|
|
30
|
+
if (!response.ok) return 0;
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
return (data.active?.length || 0) + (data.away?.length || 0);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Generate welcome banner for new users (pre-auth, no handle yet)
|
|
40
|
+
*/
|
|
41
|
+
function generatePreAuthBanner(onlineCount) {
|
|
42
|
+
const onlineText = onlineCount > 0 ? `š¢ ${onlineCount} online now` : 'š¢ join the crew';
|
|
43
|
+
return `
|
|
44
|
+
āāā ā āāā āāā š ship together
|
|
45
|
+
āāā ā āāā āāā ${onlineText}
|
|
46
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generate welcome banner for authenticated users (with handle + unread)
|
|
52
|
+
*/
|
|
53
|
+
function generateAuthBanner(handle, unreadCount, onlineCount) {
|
|
54
|
+
// Format: logo | handle + unread | tagline + online
|
|
55
|
+
// Keep alignment consistent with original banner
|
|
56
|
+
const handleCol = `@${handle}`.padEnd(16);
|
|
57
|
+
const unreadCol = unreadCount > 0 ? `š¬ ${unreadCount} unread`.padEnd(14) : `š¬ 0 messages`.padEnd(14);
|
|
58
|
+
|
|
59
|
+
return ` āāā ā āāā āāā ${handleCol} š ship together
|
|
60
|
+
āāā ā āāā āāā ${unreadCol} š¢ ${onlineCount} online
|
|
61
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Detect current git repository name
|
|
66
|
+
*/
|
|
67
|
+
function detectRepoName() {
|
|
68
|
+
try {
|
|
69
|
+
const toplevel = execSync('git rev-parse --show-toplevel 2>/dev/null', {
|
|
70
|
+
encoding: 'utf8',
|
|
71
|
+
timeout: 1000
|
|
72
|
+
}).trim();
|
|
73
|
+
// Split on forward or back slash to get repo name
|
|
74
|
+
const parts = toplevel.replace(/\\/g, '/').split('/');
|
|
75
|
+
return parts[parts.length - 1];
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Detect tech stack from package.json or file extensions
|
|
83
|
+
*/
|
|
84
|
+
function detectTechStack() {
|
|
85
|
+
const techStack = new Set();
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
// Find git root first, fallback to cwd
|
|
89
|
+
let cwd;
|
|
90
|
+
try {
|
|
91
|
+
cwd = execSync('git rev-parse --show-toplevel 2>/dev/null', {
|
|
92
|
+
encoding: 'utf8',
|
|
93
|
+
timeout: 1000
|
|
94
|
+
}).trim();
|
|
95
|
+
} catch (e) {
|
|
96
|
+
cwd = process.cwd();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Try reading package.json
|
|
100
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
101
|
+
if (fs.existsSync(pkgPath)) {
|
|
102
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
103
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
104
|
+
|
|
105
|
+
// Map common packages to tech names
|
|
106
|
+
const techMap = {
|
|
107
|
+
'react': 'React',
|
|
108
|
+
'next': 'Next.js',
|
|
109
|
+
'vue': 'Vue',
|
|
110
|
+
'svelte': 'Svelte',
|
|
111
|
+
'express': 'Express',
|
|
112
|
+
'fastify': 'Fastify',
|
|
113
|
+
'typescript': 'TypeScript',
|
|
114
|
+
'@anthropic-ai/sdk': 'Claude API',
|
|
115
|
+
'openai': 'OpenAI',
|
|
116
|
+
'langchain': 'LangChain',
|
|
117
|
+
'prisma': 'Prisma',
|
|
118
|
+
'@vercel/kv': 'Vercel KV',
|
|
119
|
+
'tailwindcss': 'Tailwind',
|
|
120
|
+
'electron': 'Electron',
|
|
121
|
+
'@tauri-apps/api': 'Tauri'
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
for (const [pkg, tech] of Object.entries(techMap)) {
|
|
125
|
+
if (deps[pkg]) techStack.add(tech);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Detect by file extensions in cwd
|
|
130
|
+
const files = fs.readdirSync(cwd).slice(0, 50); // Limit scan
|
|
131
|
+
for (const f of files) {
|
|
132
|
+
if (f.endsWith('.ts') || f.endsWith('.tsx')) techStack.add('TypeScript');
|
|
133
|
+
if (f.endsWith('.py')) techStack.add('Python');
|
|
134
|
+
if (f.endsWith('.rs')) techStack.add('Rust');
|
|
135
|
+
if (f.endsWith('.go')) techStack.add('Go');
|
|
136
|
+
if (f.endsWith('.sol')) techStack.add('Solidity');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
} catch (e) {
|
|
140
|
+
// Non-fatal - continue without tech detection
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return Array.from(techStack).slice(0, 8); // Limit to 8 techs
|
|
144
|
+
}
|
|
145
|
+
|
|
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
|
|
168
|
+
*/
|
|
169
|
+
async function sendPersonalizedWelcome(handle, oneLiner) {
|
|
170
|
+
try {
|
|
171
|
+
const repoName = detectRepoName();
|
|
172
|
+
const techStack = detectTechStack();
|
|
173
|
+
|
|
174
|
+
const response = await fetch(`${API_BASE}/api/onboarding/personalized-welcome`, {
|
|
175
|
+
method: 'POST',
|
|
176
|
+
headers: { 'Content-Type': 'application/json' },
|
|
177
|
+
body: JSON.stringify({
|
|
178
|
+
handle,
|
|
179
|
+
oneLiner,
|
|
180
|
+
repoName,
|
|
181
|
+
techStack,
|
|
182
|
+
githubProfile: null
|
|
183
|
+
})
|
|
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;
|
|
193
|
+
} catch (e) {
|
|
194
|
+
console.error('[vibe_init] Personalized welcome failed:', e.message);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const LOGIN_URL = 'https://www.slashvibe.dev/login';
|
|
200
|
+
const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
|
|
201
|
+
const AUTH_TIMEOUT_MS = 120000; // 2 minutes
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Send welcome message from @seth (founder)
|
|
205
|
+
*/
|
|
206
|
+
async function sendWelcomeMessage(handle, one_liner) {
|
|
207
|
+
try {
|
|
208
|
+
const response = await fetch(`${API_URL}/api/onboarding/welcome`, {
|
|
209
|
+
method: 'POST',
|
|
210
|
+
headers: { 'Content-Type': 'application/json' },
|
|
211
|
+
body: JSON.stringify({ handle, one_liner })
|
|
212
|
+
});
|
|
213
|
+
const result = await response.json();
|
|
214
|
+
return result.success;
|
|
215
|
+
} catch (e) {
|
|
216
|
+
console.error('[vibe_init] Welcome message failed:', e.message);
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
11
220
|
|
|
12
221
|
const definition = {
|
|
13
222
|
name: 'vibe_init',
|
|
14
|
-
description: 'Set your identity for /vibe.
|
|
223
|
+
description: 'Set your identity for /vibe. Opens browser for GitHub auth and waits for completion. Returns when auth is done.',
|
|
15
224
|
inputSchema: {
|
|
16
225
|
type: 'object',
|
|
17
226
|
properties: {
|
|
18
227
|
handle: {
|
|
19
228
|
type: 'string',
|
|
20
|
-
description: '
|
|
229
|
+
description: 'Custom handle (optional - defaults to your GitHub username)'
|
|
21
230
|
},
|
|
22
231
|
one_liner: {
|
|
23
232
|
type: 'string',
|
|
24
233
|
description: 'What are you building? (one line)'
|
|
25
234
|
}
|
|
26
235
|
},
|
|
27
|
-
required: [
|
|
236
|
+
required: []
|
|
28
237
|
}
|
|
29
238
|
};
|
|
30
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Open URL in default browser
|
|
242
|
+
*/
|
|
243
|
+
function openBrowser(url) {
|
|
244
|
+
const platform = process.platform;
|
|
245
|
+
let command;
|
|
246
|
+
|
|
247
|
+
if (platform === 'darwin') {
|
|
248
|
+
command = `open "${url}"`;
|
|
249
|
+
} else if (platform === 'win32') {
|
|
250
|
+
command = `start "" "${url}"`;
|
|
251
|
+
} else {
|
|
252
|
+
command = `xdg-open "${url}"`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
exec(command, (err) => {
|
|
256
|
+
if (err) {
|
|
257
|
+
console.error('[vibe_init] Failed to open browser:', err.message);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Wait for OAuth callback - returns Promise that resolves with handle when auth completes
|
|
264
|
+
*/
|
|
265
|
+
function waitForCallback(requestedHandle, one_liner) {
|
|
266
|
+
return new Promise((resolve, reject) => {
|
|
267
|
+
let resolved = false;
|
|
268
|
+
|
|
269
|
+
const server = http.createServer(async (req, res) => {
|
|
270
|
+
const url = new URL(req.url, `http://localhost:${CALLBACK_PORT}`);
|
|
271
|
+
|
|
272
|
+
// Handle callback
|
|
273
|
+
if (url.pathname === '/callback') {
|
|
274
|
+
const token = url.searchParams.get('token');
|
|
275
|
+
const callbackHandle = url.searchParams.get('handle');
|
|
276
|
+
|
|
277
|
+
if (token && callbackHandle) {
|
|
278
|
+
// Save the token and handle
|
|
279
|
+
const finalHandle = requestedHandle || callbackHandle;
|
|
280
|
+
|
|
281
|
+
// Save to config (file persistence for restarts)
|
|
282
|
+
config.saveAuthToken(token);
|
|
283
|
+
config.setSessionIdentity(finalHandle, one_liner || '');
|
|
284
|
+
|
|
285
|
+
// PUSH to in-memory auth store (immediate propagation)
|
|
286
|
+
authStore.setToken(token);
|
|
287
|
+
authStore.setHandle(finalHandle);
|
|
288
|
+
authStore.setOneLiner(one_liner || '');
|
|
289
|
+
|
|
290
|
+
// Update shared config
|
|
291
|
+
const cfg = config.load();
|
|
292
|
+
cfg.handle = finalHandle;
|
|
293
|
+
cfg.one_liner = one_liner || '';
|
|
294
|
+
cfg.authMethod = 'browser';
|
|
295
|
+
cfg.pendingAuth = false;
|
|
296
|
+
config.save(cfg);
|
|
297
|
+
|
|
298
|
+
// Register session with API
|
|
299
|
+
const sessionId = config.getSessionId();
|
|
300
|
+
await store.registerSession(sessionId, finalHandle, one_liner);
|
|
301
|
+
|
|
302
|
+
// Send initial heartbeat
|
|
303
|
+
await store.heartbeat(finalHandle, one_liner);
|
|
304
|
+
|
|
305
|
+
// Post to Discord
|
|
306
|
+
discord.postJoin(finalHandle, one_liner);
|
|
307
|
+
|
|
308
|
+
// NOTE: Welcome message is sent in the main return path (awaited)
|
|
309
|
+
// to ensure it arrives before we show the unread count
|
|
310
|
+
|
|
311
|
+
// Send success response to browser - lightweight, no infinite animations
|
|
312
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
313
|
+
res.end(`<!DOCTYPE html>
|
|
314
|
+
<html lang="en">
|
|
315
|
+
<head>
|
|
316
|
+
<meta charset="UTF-8">
|
|
317
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
318
|
+
<title>Welcome to /vibe!</title>
|
|
319
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
320
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
321
|
+
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&display=swap" rel="stylesheet">
|
|
322
|
+
<style>
|
|
323
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
324
|
+
|
|
325
|
+
:root {
|
|
326
|
+
--neon-green: #00FF88;
|
|
327
|
+
--neon-cyan: #00FFFF;
|
|
328
|
+
--deep-black: #0A0A0A;
|
|
329
|
+
--glow-green: 0 0 15px #00FF88;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
body {
|
|
333
|
+
font-family: 'VT323', monospace;
|
|
334
|
+
background: var(--deep-black);
|
|
335
|
+
min-height: 100vh;
|
|
336
|
+
display: flex;
|
|
337
|
+
align-items: center;
|
|
338
|
+
justify-content: center;
|
|
339
|
+
color: #fff;
|
|
340
|
+
overflow: hidden;
|
|
341
|
+
position: relative;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/* Static CRT Scanline Effect - no animation */
|
|
345
|
+
body::before {
|
|
346
|
+
content: '';
|
|
347
|
+
position: fixed;
|
|
348
|
+
top: 0; left: 0;
|
|
349
|
+
width: 100%; height: 100%;
|
|
350
|
+
background: repeating-linear-gradient(
|
|
351
|
+
0deg,
|
|
352
|
+
rgba(0, 0, 0, 0.1),
|
|
353
|
+
rgba(0, 0, 0, 0.1) 1px,
|
|
354
|
+
transparent 1px,
|
|
355
|
+
transparent 2px
|
|
356
|
+
);
|
|
357
|
+
pointer-events: none;
|
|
358
|
+
z-index: 1000;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* Static vignette */
|
|
362
|
+
body::after {
|
|
363
|
+
content: '';
|
|
364
|
+
position: fixed;
|
|
365
|
+
top: 0; left: 0;
|
|
366
|
+
width: 100%; height: 100%;
|
|
367
|
+
background: radial-gradient(ellipse at center, transparent 0%, rgba(0, 0, 0, 0.3) 100%);
|
|
368
|
+
pointer-events: none;
|
|
369
|
+
z-index: 999;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/* Static decorative symbols - NO animation */
|
|
373
|
+
.particles {
|
|
374
|
+
position: fixed;
|
|
375
|
+
top: 0; left: 0;
|
|
376
|
+
width: 100%; height: 100%;
|
|
377
|
+
pointer-events: none;
|
|
378
|
+
z-index: 1;
|
|
379
|
+
opacity: 0.15;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.particle {
|
|
383
|
+
position: absolute;
|
|
384
|
+
font-size: 20px;
|
|
385
|
+
color: var(--neon-green);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.container {
|
|
389
|
+
background: rgba(0, 255, 136, 0.03);
|
|
390
|
+
border: 2px solid var(--neon-green);
|
|
391
|
+
padding: 48px 64px;
|
|
392
|
+
max-width: 480px;
|
|
393
|
+
width: 90%;
|
|
394
|
+
text-align: center;
|
|
395
|
+
position: relative;
|
|
396
|
+
z-index: 10;
|
|
397
|
+
box-shadow: var(--glow-green);
|
|
398
|
+
animation: fadeIn 0.4s ease-out forwards;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
@keyframes fadeIn {
|
|
402
|
+
from { opacity: 0; transform: scale(0.95); }
|
|
403
|
+
to { opacity: 1; transform: scale(1); }
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/* Corner decorations */
|
|
407
|
+
.container::before, .container::after {
|
|
408
|
+
content: '+';
|
|
409
|
+
position: absolute;
|
|
410
|
+
font-family: 'VT323', monospace;
|
|
411
|
+
font-size: 24px;
|
|
412
|
+
color: var(--neon-green);
|
|
413
|
+
}
|
|
414
|
+
.container::before { top: 8px; left: 12px; }
|
|
415
|
+
.container::after { bottom: 8px; right: 12px; }
|
|
416
|
+
|
|
417
|
+
.logo {
|
|
418
|
+
font-family: 'Press Start 2P', cursive;
|
|
419
|
+
font-size: 28px;
|
|
420
|
+
color: var(--neon-green);
|
|
421
|
+
text-shadow: var(--glow-green);
|
|
422
|
+
margin-bottom: 24px;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.checkmark {
|
|
426
|
+
width: 80px;
|
|
427
|
+
height: 80px;
|
|
428
|
+
margin: 0 auto 24px;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.checkmark svg {
|
|
432
|
+
width: 100%;
|
|
433
|
+
height: 100%;
|
|
434
|
+
fill: none;
|
|
435
|
+
stroke: var(--neon-green);
|
|
436
|
+
stroke-width: 4;
|
|
437
|
+
stroke-linecap: round;
|
|
438
|
+
stroke-linejoin: round;
|
|
439
|
+
filter: drop-shadow(0 0 8px var(--neon-green));
|
|
440
|
+
animation: drawCheck 0.5s ease-out forwards;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
@keyframes drawCheck {
|
|
444
|
+
0% { stroke-dasharray: 100; stroke-dashoffset: 100; }
|
|
445
|
+
100% { stroke-dasharray: 100; stroke-dashoffset: 0; }
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.welcome {
|
|
449
|
+
font-size: 28px;
|
|
450
|
+
color: rgba(255, 255, 255, 0.9);
|
|
451
|
+
margin-bottom: 8px;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.handle {
|
|
455
|
+
color: var(--neon-green);
|
|
456
|
+
text-shadow: 0 0 10px var(--neon-green);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.status {
|
|
460
|
+
font-size: 20px;
|
|
461
|
+
color: var(--neon-cyan);
|
|
462
|
+
margin: 20px 0;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.close-msg {
|
|
466
|
+
font-size: 18px;
|
|
467
|
+
color: rgba(255, 255, 255, 0.6);
|
|
468
|
+
margin-top: 24px;
|
|
469
|
+
padding: 12px 20px;
|
|
470
|
+
border: 1px dashed rgba(255, 255, 255, 0.3);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.ascii {
|
|
474
|
+
font-size: 14px;
|
|
475
|
+
color: rgba(0, 255, 136, 0.3);
|
|
476
|
+
margin-top: 20px;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/* Respect reduced motion preference */
|
|
480
|
+
@media (prefers-reduced-motion: reduce) {
|
|
481
|
+
*, *::before, *::after {
|
|
482
|
+
animation: none !important;
|
|
483
|
+
transition: none !important;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/* Stop all animations after load */
|
|
488
|
+
.animations-stopped *,
|
|
489
|
+
.animations-stopped *::before,
|
|
490
|
+
.animations-stopped *::after {
|
|
491
|
+
animation: none !important;
|
|
492
|
+
}
|
|
493
|
+
</style>
|
|
494
|
+
</head>
|
|
495
|
+
<body>
|
|
496
|
+
<div class="particles" id="particles"></div>
|
|
497
|
+
|
|
498
|
+
<div class="container">
|
|
499
|
+
<div class="logo">/vibe</div>
|
|
500
|
+
|
|
501
|
+
<div class="checkmark">
|
|
502
|
+
<svg viewBox="0 0 52 52">
|
|
503
|
+
<circle cx="26" cy="26" r="22" stroke-opacity="0.3"/>
|
|
504
|
+
<path d="M14 27l8 8 16-16"/>
|
|
505
|
+
</svg>
|
|
506
|
+
</div>
|
|
507
|
+
|
|
508
|
+
<p class="welcome">Welcome, <span class="handle">@${finalHandle}</span></p>
|
|
509
|
+
<p class="status">Authentication successful</p>
|
|
510
|
+
|
|
511
|
+
<p class="close-msg">You can now close this window</p>
|
|
512
|
+
|
|
513
|
+
<div class="ascii">āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā</div>
|
|
514
|
+
</div>
|
|
515
|
+
|
|
516
|
+
<script>
|
|
517
|
+
// Create STATIC decorative symbols (no animation)
|
|
518
|
+
(function() {
|
|
519
|
+
var container = document.getElementById('particles');
|
|
520
|
+
var symbols = ['>', '<', '/', '*', '#', '@', '~'];
|
|
521
|
+
var positions = [
|
|
522
|
+
{x: 10, y: 15}, {x: 85, y: 20}, {x: 20, y: 75}, {x: 75, y: 80},
|
|
523
|
+
{x: 50, y: 10}, {x: 15, y: 45}, {x: 88, y: 55}, {x: 45, y: 85}
|
|
524
|
+
];
|
|
525
|
+
positions.forEach(function(pos, i) {
|
|
526
|
+
var particle = document.createElement('div');
|
|
527
|
+
particle.className = 'particle';
|
|
528
|
+
particle.textContent = symbols[i % symbols.length];
|
|
529
|
+
particle.style.left = pos.x + '%';
|
|
530
|
+
particle.style.top = pos.y + '%';
|
|
531
|
+
container.appendChild(particle);
|
|
532
|
+
});
|
|
533
|
+
})();
|
|
534
|
+
|
|
535
|
+
// Stop any remaining animations after 2 seconds (safety net)
|
|
536
|
+
setTimeout(function() {
|
|
537
|
+
document.body.classList.add('animations-stopped');
|
|
538
|
+
}, 2000);
|
|
539
|
+
</script>
|
|
540
|
+
</body>
|
|
541
|
+
</html>`);
|
|
542
|
+
|
|
543
|
+
// Close server and resolve
|
|
544
|
+
resolved = true;
|
|
545
|
+
setTimeout(() => server.close(), 500);
|
|
546
|
+
resolve({ success: true, handle: finalHandle });
|
|
547
|
+
} else {
|
|
548
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
549
|
+
res.end('Missing token or handle');
|
|
550
|
+
}
|
|
551
|
+
} else {
|
|
552
|
+
res.writeHead(404);
|
|
553
|
+
res.end('Not found');
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
server.on('error', (err) => {
|
|
558
|
+
if (err.code === 'EADDRINUSE') {
|
|
559
|
+
reject(new Error('AUTH_IN_PROGRESS'));
|
|
560
|
+
} else {
|
|
561
|
+
reject(err);
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// Start server
|
|
566
|
+
server.listen(CALLBACK_PORT, '127.0.0.1', () => {
|
|
567
|
+
console.log(`[vibe_init] Callback server listening on port ${CALLBACK_PORT}`);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// Timeout after 2 minutes
|
|
571
|
+
setTimeout(() => {
|
|
572
|
+
if (!resolved) {
|
|
573
|
+
server.close();
|
|
574
|
+
reject(new Error('AUTH_TIMEOUT'));
|
|
575
|
+
}
|
|
576
|
+
}, AUTH_TIMEOUT_MS);
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
31
580
|
async function handler(args) {
|
|
32
|
-
const { handle, one_liner } = args;
|
|
581
|
+
const { handle, one_liner, auth_method } = args;
|
|
33
582
|
|
|
34
|
-
// Normalize handle
|
|
35
|
-
const h = handle
|
|
583
|
+
// Normalize handle if provided
|
|
584
|
+
const h = handle
|
|
585
|
+
? handle.toLowerCase().replace('@', '').replace(/[^a-z0-9_-]/g, '')
|
|
586
|
+
: null;
|
|
36
587
|
|
|
37
|
-
|
|
588
|
+
// Validate if custom handle provided
|
|
589
|
+
if (h && h.length < 2) {
|
|
38
590
|
return {
|
|
39
591
|
display: 'Handle must be at least 2 characters (letters, numbers, - or _)'
|
|
40
592
|
};
|
|
41
593
|
}
|
|
42
594
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
595
|
+
// Check if already authenticated
|
|
596
|
+
if (config.hasOAuth()) {
|
|
597
|
+
const existingHandle = config.getHandle();
|
|
598
|
+
if (existingHandle) {
|
|
599
|
+
return {
|
|
600
|
+
display: `## Already signed in as @${existingHandle}
|
|
601
|
+
|
|
602
|
+
To sign out and re-authenticate: \`vibe logout\`
|
|
603
|
+
To see who's online: \`vibe who\`
|
|
604
|
+
To check messages: \`vibe inbox\``
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// ===========================================
|
|
610
|
+
// Show welcome banner (pre-auth)
|
|
611
|
+
// ===========================================
|
|
612
|
+
const onlineCount = await getOnlineCount();
|
|
613
|
+
const welcomeBanner = generatePreAuthBanner(onlineCount);
|
|
614
|
+
|
|
615
|
+
// ===========================================
|
|
616
|
+
// BROWSER AUTH (Default): GitHub OAuth
|
|
617
|
+
// ===========================================
|
|
618
|
+
if (auth_method === 'browser' || !auth_method) {
|
|
619
|
+
// Save one_liner for callback handler
|
|
620
|
+
const cfg = config.load();
|
|
621
|
+
if (h) cfg.handle = h;
|
|
622
|
+
cfg.one_liner = one_liner || '';
|
|
623
|
+
cfg.pendingAuth = true;
|
|
624
|
+
config.save(cfg);
|
|
625
|
+
|
|
626
|
+
// Build login URL with redirect to our local callback
|
|
627
|
+
const callbackUrl = `http://localhost:${CALLBACK_PORT}/callback`;
|
|
628
|
+
const loginUrl = h
|
|
629
|
+
? `${LOGIN_URL}?redirect=${encodeURIComponent(callbackUrl)}&handle=${encodeURIComponent(h)}`
|
|
630
|
+
: `${LOGIN_URL}?redirect=${encodeURIComponent(callbackUrl)}`;
|
|
631
|
+
|
|
632
|
+
// Open browser BEFORE starting to wait
|
|
633
|
+
openBrowser(loginUrl);
|
|
634
|
+
|
|
635
|
+
try {
|
|
636
|
+
// Wait for callback (blocks until auth completes or times out)
|
|
637
|
+
const result = await waitForCallback(h, one_liner);
|
|
638
|
+
|
|
639
|
+
// Send personalized welcome and wait for it (2.5s timeout)
|
|
640
|
+
let welcomeResult = null;
|
|
641
|
+
try {
|
|
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
|
+
]);
|
|
664
|
+
} catch (e) {}
|
|
665
|
+
|
|
666
|
+
// Generate authenticated banner with handle + unread (3 lines only - won't collapse)
|
|
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
|
+
}
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
display: authBanner + friendsSection + welcomeSection,
|
|
688
|
+
onboarding: {
|
|
689
|
+
isNewUser: true,
|
|
690
|
+
handle: result.handle,
|
|
691
|
+
hint: 'show_onboarding_options',
|
|
692
|
+
hasWelcomeMessage: !!welcomeResult,
|
|
693
|
+
welcomeText: welcomeResult?.messageText || null,
|
|
694
|
+
friendsOnVibe: friendsData?.friendsOnVibe || [],
|
|
695
|
+
inviteSuggestions: friendsData?.inviteSuggestions || []
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
} catch (err) {
|
|
700
|
+
if (err.message === 'AUTH_IN_PROGRESS') {
|
|
701
|
+
return {
|
|
702
|
+
display: `## Auth already in progress
|
|
703
|
+
|
|
704
|
+
Another login flow is running. Complete it in your browser or wait a moment and try again.`
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if (err.message === 'AUTH_TIMEOUT') {
|
|
709
|
+
return {
|
|
710
|
+
display: `## Auth timed out
|
|
711
|
+
|
|
712
|
+
The login flow wasn't completed within 2 minutes. Try again with \`vibe init\``
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return {
|
|
717
|
+
display: `## Failed to authenticate
|
|
718
|
+
|
|
719
|
+
Error: ${err.message}
|
|
720
|
+
|
|
721
|
+
Try again or use legacy auth: \`vibe init --auth_method=legacy\``
|
|
722
|
+
};
|
|
723
|
+
}
|
|
48
724
|
}
|
|
49
725
|
|
|
50
|
-
//
|
|
726
|
+
// ===========================================
|
|
727
|
+
// LEGACY AUTH: Local Ed25519 keypairs
|
|
728
|
+
// ===========================================
|
|
729
|
+
const crypto = require('../crypto');
|
|
730
|
+
|
|
731
|
+
// Generate Ed25519 keypair if not already present
|
|
51
732
|
let keypair = config.getKeypair();
|
|
52
733
|
let keypairNote = '';
|
|
53
734
|
if (!keypair) {
|
|
54
735
|
keypair = crypto.generateKeypair();
|
|
55
|
-
// Save keypair to shared config (persists across MCP invocations)
|
|
56
736
|
config.saveKeypair(keypair);
|
|
57
737
|
keypairNote = '\nš _AIRC keypair generated for message signing_';
|
|
58
738
|
}
|
|
59
739
|
|
|
60
|
-
// Save identity
|
|
740
|
+
// Save identity
|
|
61
741
|
config.setSessionIdentity(h, one_liner || '', keypair);
|
|
62
742
|
|
|
63
|
-
// Also update shared config for backward compat
|
|
64
743
|
const cfg = config.load();
|
|
65
744
|
cfg.handle = h;
|
|
66
745
|
cfg.one_liner = one_liner || '';
|
|
67
746
|
cfg.visible = true;
|
|
747
|
+
cfg.authMethod = 'legacy';
|
|
68
748
|
config.save(cfg);
|
|
69
749
|
|
|
70
|
-
//
|
|
750
|
+
// Register session with API
|
|
71
751
|
const sessionId = config.getSessionId();
|
|
72
|
-
|
|
73
|
-
// Register session with API (maps sessionId ā handle)
|
|
74
|
-
// Also registers user in users DB for @vibe welcome tracking
|
|
75
|
-
// AIRC: Include public key for identity verification
|
|
76
752
|
const registration = await store.registerSession(sessionId, h, one_liner, keypair.publicKey);
|
|
753
|
+
|
|
77
754
|
if (!registration.success) {
|
|
78
755
|
return {
|
|
79
756
|
display: `## Identity Set (Local Only)
|
|
@@ -89,9 +766,21 @@ Local config saved. Heartbeats will use username fallback.`
|
|
|
89
766
|
// Send initial heartbeat
|
|
90
767
|
await store.heartbeat(h, one_liner);
|
|
91
768
|
|
|
92
|
-
// Post to Discord
|
|
769
|
+
// Post to Discord
|
|
93
770
|
discord.postJoin(h, one_liner);
|
|
94
771
|
|
|
772
|
+
// Send personalized welcome from @vibe (non-blocking)
|
|
773
|
+
// Send personalized welcome and wait for it
|
|
774
|
+
let welcomeResult = null;
|
|
775
|
+
try {
|
|
776
|
+
welcomeResult = await Promise.race([
|
|
777
|
+
sendPersonalizedWelcome(h, one_liner),
|
|
778
|
+
new Promise(resolve => setTimeout(() => resolve(null), 2500))
|
|
779
|
+
]);
|
|
780
|
+
} catch (e) {
|
|
781
|
+
console.error('[vibe_init] Welcome message failed:', e.message);
|
|
782
|
+
}
|
|
783
|
+
|
|
95
784
|
// Check for unread messages
|
|
96
785
|
let unreadNotice = '';
|
|
97
786
|
try {
|
|
@@ -102,23 +791,25 @@ Local config saved. Heartbeats will use username fallback.`
|
|
|
102
791
|
} catch (e) {}
|
|
103
792
|
|
|
104
793
|
return {
|
|
105
|
-
display:
|
|
794
|
+
display: `${welcomeBanner}
|
|
795
|
+
## Welcome to /vibe! (Legacy Auth)
|
|
106
796
|
|
|
107
|
-
**@${h}**
|
|
108
|
-
_${one_liner}_${unreadNotice}${
|
|
797
|
+
**@${h}**
|
|
798
|
+
_${one_liner}_${unreadNotice}${keypairNote}
|
|
109
799
|
|
|
110
|
-
**
|
|
111
|
-
1. Say "who's around?" to see active builders
|
|
112
|
-
2. Say "message @sethgoldstein hello!" to connect
|
|
113
|
-
3. Say "I'm shipping" to set your status
|
|
800
|
+
šØ **Check your messages** ā @seth sent you a personalized welcome!
|
|
114
801
|
|
|
115
|
-
**
|
|
116
|
-
|
|
802
|
+
ā ļø **Using local keys** ā consider upgrading to GitHub auth:
|
|
803
|
+
\`vibe init\` ā Sign in with GitHub for verified identity
|
|
117
804
|
|
|
118
|
-
|
|
119
|
-
|
|
805
|
+
### Onboarding Checklist
|
|
806
|
+
[ ] Read your welcome message from @seth
|
|
807
|
+
[ ] Reply to @seth
|
|
808
|
+
[ ] Message one recommended builder
|
|
809
|
+
[ ] Post your first ship
|
|
810
|
+
[ ] Leave some feedback
|
|
120
811
|
|
|
121
|
-
|
|
812
|
+
_Say "vibe onboarding" anytime to check your progress_`
|
|
122
813
|
};
|
|
123
814
|
}
|
|
124
815
|
|