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
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Interest & Tag Suggestions ā Help users discover what to tag themselves with
|
|
3
|
+
*
|
|
4
|
+
* Analyzes what someone is building and suggests:
|
|
5
|
+
* - Interest tags (broad categories: ai, fintech, gaming)
|
|
6
|
+
* - Skill tags (specific tech: react, python, figma)
|
|
7
|
+
* - Discovery hints (who else to connect with)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const userProfiles = require('../store/profiles');
|
|
11
|
+
|
|
12
|
+
// Tech stack detection patterns
|
|
13
|
+
const TECH_PATTERNS = {
|
|
14
|
+
// Frontend
|
|
15
|
+
'react': ['react', 'jsx', 'create-react-app', 'react native'],
|
|
16
|
+
'vue': ['vue', 'vuejs', 'vue.js'],
|
|
17
|
+
'angular': ['angular', 'ng', '@angular'],
|
|
18
|
+
'svelte': ['svelte', 'sveltekit'],
|
|
19
|
+
'typescript': ['typescript', 'ts', '.ts', 'tsc'],
|
|
20
|
+
'javascript': ['javascript', 'js', 'node', 'npm'],
|
|
21
|
+
|
|
22
|
+
// Backend
|
|
23
|
+
'python': ['python', 'django', 'flask', 'fastapi', 'py'],
|
|
24
|
+
'node': ['node', 'nodejs', 'express', 'koa'],
|
|
25
|
+
'go': ['golang', 'go', 'gin', 'fiber'],
|
|
26
|
+
'rust': ['rust', 'cargo', 'actix', 'warp'],
|
|
27
|
+
'java': ['java', 'spring', 'maven', 'gradle'],
|
|
28
|
+
|
|
29
|
+
// Data & AI
|
|
30
|
+
'ai': ['ai', 'llm', 'gpt', 'claude', 'openai', 'machine learning', 'ml'],
|
|
31
|
+
'data': ['data', 'pandas', 'numpy', 'jupyter', 'sql', 'database'],
|
|
32
|
+
'ml': ['tensorflow', 'pytorch', 'scikit', 'keras', 'neural'],
|
|
33
|
+
|
|
34
|
+
// Mobile
|
|
35
|
+
'ios': ['ios', 'swift', 'swiftui', 'xcode', 'iphone', 'ipad'],
|
|
36
|
+
'android': ['android', 'kotlin', 'java android', 'play store'],
|
|
37
|
+
'flutter': ['flutter', 'dart'],
|
|
38
|
+
'react-native': ['react native', 'expo', 'react-native'],
|
|
39
|
+
|
|
40
|
+
// Design
|
|
41
|
+
'design': ['figma', 'sketch', 'adobe', 'ui', 'ux', 'design'],
|
|
42
|
+
'frontend': ['css', 'html', 'sass', 'tailwind', 'bootstrap'],
|
|
43
|
+
|
|
44
|
+
// Infrastructure
|
|
45
|
+
'devops': ['docker', 'kubernetes', 'aws', 'gcp', 'azure', 'terraform'],
|
|
46
|
+
'database': ['postgres', 'mysql', 'mongodb', 'redis', 'sqlite'],
|
|
47
|
+
|
|
48
|
+
// Domains
|
|
49
|
+
'crypto': ['crypto', 'blockchain', 'web3', 'ethereum', 'bitcoin', 'defi'],
|
|
50
|
+
'gaming': ['game', 'unity', 'unreal', 'godot', 'gamedev'],
|
|
51
|
+
'fintech': ['fintech', 'payments', 'banking', 'trading', 'finance'],
|
|
52
|
+
'healthcare': ['health', 'medical', 'biotech', 'pharma'],
|
|
53
|
+
'ecommerce': ['ecommerce', 'shopify', 'stripe', 'payment'],
|
|
54
|
+
'saas': ['saas', 'b2b', 'enterprise', 'subscription'],
|
|
55
|
+
'social': ['social', 'community', 'chat', 'messaging'],
|
|
56
|
+
'productivity': ['productivity', 'workflow', 'automation', 'tool']
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Interest categories (broader themes)
|
|
60
|
+
const INTEREST_PATTERNS = {
|
|
61
|
+
'startups': ['startup', 'founder', 'entrepreneurship', 'indie hacker', 'side project'],
|
|
62
|
+
'open source': ['open source', 'oss', 'github', 'contribute', 'maintainer'],
|
|
63
|
+
'developer tools': ['dev tool', 'cli', 'vscode', 'editor', 'developer experience'],
|
|
64
|
+
'automation': ['automation', 'workflow', 'scripting', 'no-code', 'zapier'],
|
|
65
|
+
'content creation': ['content', 'blog', 'writing', 'video', 'podcast', 'creator'],
|
|
66
|
+
'education': ['education', 'learning', 'teaching', 'course', 'tutorial'],
|
|
67
|
+
'remote work': ['remote', 'distributed', 'async', 'nomad', 'wfh'],
|
|
68
|
+
'indie': ['indie', 'bootstrap', 'solo', 'maker', 'build in public']
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Analyze text and suggest tags
|
|
72
|
+
function suggestTags(buildingText) {
|
|
73
|
+
if (!buildingText) return { tech: [], interests: [], confidence: 0 };
|
|
74
|
+
|
|
75
|
+
const text = buildingText.toLowerCase();
|
|
76
|
+
const suggestions = {
|
|
77
|
+
tech: new Set(),
|
|
78
|
+
interests: new Set(),
|
|
79
|
+
matches: []
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Find tech matches
|
|
83
|
+
for (const [tag, patterns] of Object.entries(TECH_PATTERNS)) {
|
|
84
|
+
for (const pattern of patterns) {
|
|
85
|
+
if (text.includes(pattern.toLowerCase())) {
|
|
86
|
+
suggestions.tech.add(tag);
|
|
87
|
+
suggestions.matches.push({ tag, pattern, type: 'tech' });
|
|
88
|
+
break; // Only count once per tag
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Find interest matches
|
|
94
|
+
for (const [interest, patterns] of Object.entries(INTEREST_PATTERNS)) {
|
|
95
|
+
for (const pattern of patterns) {
|
|
96
|
+
if (text.includes(pattern.toLowerCase())) {
|
|
97
|
+
suggestions.interests.add(interest);
|
|
98
|
+
suggestions.matches.push({ tag: interest, pattern, type: 'interest' });
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Calculate confidence based on number of matches
|
|
105
|
+
const totalMatches = suggestions.matches.length;
|
|
106
|
+
const confidence = Math.min(totalMatches * 20, 100); // Cap at 100%
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
tech: Array.from(suggestions.tech).slice(0, 5), // Limit suggestions
|
|
110
|
+
interests: Array.from(suggestions.interests).slice(0, 3),
|
|
111
|
+
confidence,
|
|
112
|
+
matches: suggestions.matches
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Find similar builders based on tags
|
|
117
|
+
async function findSimilarBuilders(suggestedTags, limit = 3) {
|
|
118
|
+
const allProfiles = await userProfiles.getAllProfiles();
|
|
119
|
+
const matches = [];
|
|
120
|
+
|
|
121
|
+
for (const profile of allProfiles) {
|
|
122
|
+
if (!profile.tags && !profile.interests) continue;
|
|
123
|
+
|
|
124
|
+
let overlap = 0;
|
|
125
|
+
const overlappingTags = [];
|
|
126
|
+
|
|
127
|
+
// Check tech tag overlap
|
|
128
|
+
if (profile.tags) {
|
|
129
|
+
for (const tag of suggestedTags.tech) {
|
|
130
|
+
if (profile.tags.includes(tag)) {
|
|
131
|
+
overlap += 2; // Tech overlap is worth more
|
|
132
|
+
overlappingTags.push(tag);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check interest overlap
|
|
138
|
+
if (profile.interests) {
|
|
139
|
+
for (const interest of suggestedTags.interests) {
|
|
140
|
+
if (profile.interests.includes(interest)) {
|
|
141
|
+
overlap += 1;
|
|
142
|
+
overlappingTags.push(interest);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (overlap > 0) {
|
|
148
|
+
matches.push({
|
|
149
|
+
handle: profile.handle,
|
|
150
|
+
overlap,
|
|
151
|
+
overlappingTags,
|
|
152
|
+
building: profile.building,
|
|
153
|
+
lastSeen: profile.lastSeen
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return matches
|
|
159
|
+
.sort((a, b) => b.overlap - a.overlap)
|
|
160
|
+
.slice(0, limit);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Generate tag suggestions for a user
|
|
164
|
+
async function generateSuggestions(handle) {
|
|
165
|
+
const profile = await userProfiles.getProfile(handle);
|
|
166
|
+
|
|
167
|
+
if (!profile.building) {
|
|
168
|
+
return {
|
|
169
|
+
error: 'No building info found. Set what you\'re building first:\n`vibe update building "your project description"`'
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const suggestions = suggestTags(profile.building);
|
|
174
|
+
const similarBuilders = await findSimilarBuilders(suggestions);
|
|
175
|
+
|
|
176
|
+
// Filter out already set tags
|
|
177
|
+
const currentTags = new Set([...(profile.tags || []), ...(profile.interests || [])]);
|
|
178
|
+
const newTechTags = suggestions.tech.filter(t => !currentTags.has(t));
|
|
179
|
+
const newInterests = suggestions.interests.filter(i => !currentTags.has(i));
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
building: profile.building,
|
|
183
|
+
confidence: suggestions.confidence,
|
|
184
|
+
currentTags: profile.tags || [],
|
|
185
|
+
currentInterests: profile.interests || [],
|
|
186
|
+
suggested: {
|
|
187
|
+
tech: newTechTags,
|
|
188
|
+
interests: newInterests
|
|
189
|
+
},
|
|
190
|
+
similarBuilders,
|
|
191
|
+
matches: suggestions.matches
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Get trending tags across all users
|
|
196
|
+
async function getTrendingTags() {
|
|
197
|
+
const allTags = await userProfiles.getTrendingTags();
|
|
198
|
+
const allInterests = await userProfiles.getTrendingInterests();
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
popularTags: allTags.slice(0, 10),
|
|
202
|
+
popularInterests: allInterests.slice(0, 8)
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Smart tag completion - suggest similar tags based on partial input
|
|
207
|
+
function suggestSimilarTags(partial) {
|
|
208
|
+
const allTags = Object.keys(TECH_PATTERNS);
|
|
209
|
+
const allInterests = Object.keys(INTEREST_PATTERNS);
|
|
210
|
+
|
|
211
|
+
const search = partial.toLowerCase();
|
|
212
|
+
const matches = [];
|
|
213
|
+
|
|
214
|
+
// Exact prefix matches first
|
|
215
|
+
for (const tag of [...allTags, ...allInterests]) {
|
|
216
|
+
if (tag.startsWith(search)) {
|
|
217
|
+
matches.push({ tag, type: allTags.includes(tag) ? 'tech' : 'interest', score: 3 });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Substring matches second
|
|
222
|
+
for (const tag of [...allTags, ...allInterests]) {
|
|
223
|
+
if (tag.includes(search) && !tag.startsWith(search)) {
|
|
224
|
+
matches.push({ tag, type: allTags.includes(tag) ? 'tech' : 'interest', score: 2 });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Pattern matches third (e.g., "react" matches "react native")
|
|
229
|
+
for (const [tag, patterns] of [...Object.entries(TECH_PATTERNS), ...Object.entries(INTEREST_PATTERNS)]) {
|
|
230
|
+
for (const pattern of patterns) {
|
|
231
|
+
if (pattern.includes(search) && !tag.includes(search)) {
|
|
232
|
+
matches.push({
|
|
233
|
+
tag,
|
|
234
|
+
type: Object.keys(TECH_PATTERNS).includes(tag) ? 'tech' : 'interest',
|
|
235
|
+
score: 1,
|
|
236
|
+
matchedPattern: pattern
|
|
237
|
+
});
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return matches
|
|
244
|
+
.sort((a, b) => b.score - a.score)
|
|
245
|
+
.slice(0, 8)
|
|
246
|
+
.map(m => m.tag);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
module.exports = {
|
|
250
|
+
suggestTags,
|
|
251
|
+
generateSuggestions,
|
|
252
|
+
findSimilarBuilders,
|
|
253
|
+
getTrendingTags,
|
|
254
|
+
suggestSimilarTags,
|
|
255
|
+
TECH_PATTERNS,
|
|
256
|
+
INTEREST_PATTERNS
|
|
257
|
+
};
|
package/tools/tip.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe_tip - Send an instant on-chain USDC tip
|
|
3
|
+
*
|
|
4
|
+
* Real crypto tips with instant settlement on Base Sepolia.
|
|
5
|
+
* No fees - creator gets 100% of the tip.
|
|
6
|
+
* Gas is sponsored by /vibe.
|
|
7
|
+
*
|
|
8
|
+
* Examples:
|
|
9
|
+
* - "tip @alice $5 for helping debug"
|
|
10
|
+
* - "send @bob $1 thanks for the intro"
|
|
11
|
+
* - "vibe tip @charlie 10"
|
|
12
|
+
*
|
|
13
|
+
* Preset amounts: $1 (default), $5, $10
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const config = require('../config');
|
|
17
|
+
const { requireInit, normalizeHandle, displayHandle } = require('./_shared');
|
|
18
|
+
const { actions, formatActions } = require('./_actions');
|
|
19
|
+
|
|
20
|
+
// Preset tip amounts in cents
|
|
21
|
+
const TIP_PRESETS = {
|
|
22
|
+
1: 100, // $1
|
|
23
|
+
5: 500, // $5
|
|
24
|
+
10: 1000 // $10
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const definition = {
|
|
28
|
+
name: 'vibe_tip',
|
|
29
|
+
description: 'Send an instant on-chain USDC tip. 0% fee - creator gets 100%. Gas is sponsored. Amounts: $1 (default), $5, $10.',
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
to: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'Recipient handle (e.g., @alice)'
|
|
36
|
+
},
|
|
37
|
+
amount: {
|
|
38
|
+
type: 'number',
|
|
39
|
+
description: 'Amount in USD (1, 5, or 10). Default: 1'
|
|
40
|
+
},
|
|
41
|
+
message: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
description: 'Optional message to include with the tip'
|
|
44
|
+
},
|
|
45
|
+
context: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
description: 'Optional context (type: dm_reply|ship|profile|gig_bonus, reference_id)',
|
|
48
|
+
properties: {
|
|
49
|
+
type: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
enum: ['dm_reply', 'ship', 'profile', 'gig_bonus']
|
|
52
|
+
},
|
|
53
|
+
reference_id: {
|
|
54
|
+
type: 'string'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
required: ['to']
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
async function handler(args) {
|
|
64
|
+
const initCheck = requireInit();
|
|
65
|
+
if (initCheck) return initCheck;
|
|
66
|
+
|
|
67
|
+
const { to, message, context } = args;
|
|
68
|
+
let { amount = 1 } = args;
|
|
69
|
+
|
|
70
|
+
const myHandle = config.getHandle();
|
|
71
|
+
const token = config.getToken();
|
|
72
|
+
|
|
73
|
+
if (!token) {
|
|
74
|
+
return {
|
|
75
|
+
display: 'ā **Not authenticated**\n\nRun `vibe init` to authenticate with GitHub first.'
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Normalize recipient handle
|
|
80
|
+
const recipient = normalizeHandle(to);
|
|
81
|
+
if (!recipient) {
|
|
82
|
+
return {
|
|
83
|
+
display: 'ā **Missing recipient**\n\nPlease specify who to tip (e.g., `tip @alice 5`).'
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Validate and normalize amount to preset
|
|
88
|
+
amount = parseFloat(amount);
|
|
89
|
+
if (isNaN(amount) || amount <= 0) {
|
|
90
|
+
amount = 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Round to nearest preset
|
|
94
|
+
let amountCents;
|
|
95
|
+
if (amount >= 7.5) {
|
|
96
|
+
amountCents = TIP_PRESETS[10];
|
|
97
|
+
amount = 10;
|
|
98
|
+
} else if (amount >= 3) {
|
|
99
|
+
amountCents = TIP_PRESETS[5];
|
|
100
|
+
amount = 5;
|
|
101
|
+
} else {
|
|
102
|
+
amountCents = TIP_PRESETS[1];
|
|
103
|
+
amount = 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Call instant tip API
|
|
107
|
+
const apiUrl = config.getApiUrl();
|
|
108
|
+
|
|
109
|
+
// Generate idempotency key to prevent duplicate tips from retries
|
|
110
|
+
// Key is based on: sender + recipient + amount + timestamp (1-minute bucket)
|
|
111
|
+
const timeBucket = Math.floor(Date.now() / 60000); // 1-minute buckets
|
|
112
|
+
const idempotencyKey = `${myHandle}:${recipient}:${amountCents}:${timeBucket}`;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const response = await fetch(`${apiUrl}/api/tips/instant`, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers: {
|
|
118
|
+
'Content-Type': 'application/json',
|
|
119
|
+
'Authorization': `Bearer ${token}`,
|
|
120
|
+
'Idempotency-Key': idempotencyKey
|
|
121
|
+
},
|
|
122
|
+
body: JSON.stringify({
|
|
123
|
+
to: recipient,
|
|
124
|
+
amount_cents: amountCents,
|
|
125
|
+
message,
|
|
126
|
+
context
|
|
127
|
+
})
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const result = await response.json();
|
|
131
|
+
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
// Handle specific error cases
|
|
134
|
+
if (result.error === 'insufficient_balance') {
|
|
135
|
+
return {
|
|
136
|
+
display: `ā **Insufficient USDC balance**\n\nYou need USDC to tip. Your balance: $${result.balance_usdc}\n\n` +
|
|
137
|
+
`Get USDC at [bridge.base.org](${result.help_url})`
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (result.error === 'recipient_no_wallet') {
|
|
142
|
+
return {
|
|
143
|
+
display: `ā **${displayHandle(recipient)} can't receive tips yet**\n\n` +
|
|
144
|
+
`They need to complete their wallet setup first.`
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (result.error === 'rate_limited') {
|
|
149
|
+
return {
|
|
150
|
+
display: `ā³ **Rate limit reached**\n\n${result.message}\n\nTry again in ${Math.ceil(result.reset_in_seconds / 60)} minutes.`
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
display: `ā **Tip failed**\n\n${result.message || 'Please try again.'}`
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Success!
|
|
160
|
+
let display = `ā
**Tipped ${displayHandle(recipient)} $${amount} USDC**\n\n`;
|
|
161
|
+
|
|
162
|
+
display += `š° Amount: $${amount}.00\n`;
|
|
163
|
+
display += `š¤ To: ${displayHandle(recipient)}\n`;
|
|
164
|
+
display += `š„ Fee: $0.00 (0%)\n`;
|
|
165
|
+
display += `⨠Net to creator: $${amount}.00\n`;
|
|
166
|
+
|
|
167
|
+
if (message) {
|
|
168
|
+
display += `\nš¬ _"${message}"_\n`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
display += `\nš [View on Explorer](${result.explorer_url})`;
|
|
172
|
+
|
|
173
|
+
// Build response with actions
|
|
174
|
+
const response_obj = {
|
|
175
|
+
display,
|
|
176
|
+
tx_hash: result.tx_hash,
|
|
177
|
+
explorer_url: result.explorer_url
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Add follow-up actions
|
|
181
|
+
response_obj.actions = formatActions(actions.afterTip ? actions.afterTip(recipient) : actions.afterDm(recipient));
|
|
182
|
+
|
|
183
|
+
return response_obj;
|
|
184
|
+
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error('[tip] Error:', error);
|
|
187
|
+
return {
|
|
188
|
+
display: `ā **Tip failed**\n\nError: ${error.message}\n\nPlease try again.`
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = { definition, handler };
|
package/tools/token.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe token ā Set auth token after GitHub OAuth
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* vibe token <paste-token-here>
|
|
6
|
+
*
|
|
7
|
+
* After authenticating via GitHub in browser, paste the token here
|
|
8
|
+
* to complete authentication.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const config = require('../config');
|
|
12
|
+
const store = require('../store');
|
|
13
|
+
|
|
14
|
+
const definition = {
|
|
15
|
+
name: 'vibe_token',
|
|
16
|
+
description: 'Set your auth token after browser authentication. Use after completing GitHub OAuth in browser.',
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
token: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'The auth token from browser authentication'
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
required: ['token']
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
async function handler(args) {
|
|
30
|
+
const { token } = args;
|
|
31
|
+
|
|
32
|
+
if (!token || token.trim().length < 10) {
|
|
33
|
+
return {
|
|
34
|
+
display: `ā **Invalid token**
|
|
35
|
+
|
|
36
|
+
Please paste the full token from your browser authentication.
|
|
37
|
+
|
|
38
|
+
If you haven't authenticated yet:
|
|
39
|
+
1. Run \`vibe init @yourhandle\` to start
|
|
40
|
+
2. Open the auth URL in your browser
|
|
41
|
+
3. Complete GitHub authentication
|
|
42
|
+
4. Paste the token here`
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Verify token with server
|
|
47
|
+
const verification = await store.verifyAuthToken(token.trim());
|
|
48
|
+
|
|
49
|
+
if (!verification.valid) {
|
|
50
|
+
return {
|
|
51
|
+
display: `ā **Token verification failed**
|
|
52
|
+
|
|
53
|
+
Error: ${verification.error}
|
|
54
|
+
|
|
55
|
+
The token may be expired or invalid. Try authenticating again:
|
|
56
|
+
1. Visit: ${config.getApiUrl()}/api/auth/github?handle=${config.getHandle() || 'yourhandle'}
|
|
57
|
+
2. Complete GitHub authentication
|
|
58
|
+
3. Paste the new token here`
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Save token
|
|
63
|
+
config.saveAuthToken(token.trim());
|
|
64
|
+
|
|
65
|
+
// Update session identity with verified handle
|
|
66
|
+
const handle = verification.handle;
|
|
67
|
+
const oneLiner = config.getOneLiner() || 'Building something';
|
|
68
|
+
config.setSessionIdentity(handle, oneLiner);
|
|
69
|
+
|
|
70
|
+
// Update shared config
|
|
71
|
+
const cfg = config.load();
|
|
72
|
+
cfg.handle = handle;
|
|
73
|
+
cfg.one_liner = oneLiner;
|
|
74
|
+
config.save(cfg);
|
|
75
|
+
|
|
76
|
+
// Remove old keypair (security improvement)
|
|
77
|
+
if (config.hasKeypair()) {
|
|
78
|
+
config.removeKeypair();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Send initial heartbeat
|
|
82
|
+
await store.heartbeat(handle, oneLiner);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
display: `ā
**Authenticated as @${handle}**
|
|
86
|
+
|
|
87
|
+
GitHub: @${verification.github || 'linked'}
|
|
88
|
+
Expires: ${verification.expiresAt ? new Date(verification.expiresAt * 1000).toLocaleString() : 'unknown'}
|
|
89
|
+
|
|
90
|
+
Your messages are now signed server-side (more secure).
|
|
91
|
+
${config.hasKeypair() ? '' : 'š Old local keys have been removed.'}
|
|
92
|
+
|
|
93
|
+
You're ready to vibe! Try:
|
|
94
|
+
⢠\`vibe who\` ā See who's online
|
|
95
|
+
⢠\`vibe inbox\` ā Check messages
|
|
96
|
+
⢠\`vibe dm @someone "hello"\` ā Send a message`,
|
|
97
|
+
handle,
|
|
98
|
+
github: verification.github,
|
|
99
|
+
authMethod: 'github'
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { definition, handler };
|
package/tools/update.js
CHANGED
|
@@ -49,7 +49,7 @@ async function handler(args) {
|
|
|
49
49
|
display += `Your install doesn't support automatic updates.\n\n`;
|
|
50
50
|
display += `To update, re-run the installer:\n`;
|
|
51
51
|
display += `\`\`\`\n`;
|
|
52
|
-
display += `curl -fsSL https://raw.githubusercontent.com/
|
|
52
|
+
display += `curl -fsSL https://raw.githubusercontent.com/VibeCodingInc/vibe-mcp/main/install.sh | bash\n`;
|
|
53
53
|
display += `\`\`\`\n\n`;
|
|
54
54
|
display += `This will migrate you to the git-based installer for future updates.\n\n`;
|
|
55
55
|
display += `---\n`;
|