slashvibe-mcp 0.2.2 → 0.2.4
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/config.js +174 -3
- package/index.js +163 -34
- 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/notification-emitter.js +77 -0
- package/notify.js +141 -18
- package/package.json +14 -6
- package/presence.js +5 -1
- package/protocol/index.js +88 -1
- package/protocol/telegram-commands.js +199 -0
- package/store/api.js +469 -29
- package/store/index.js +7 -7
- package/store/local.js +67 -11
- package/store/profiles.js +435 -0
- package/store/reservations.js +321 -0
- package/store/skills.js +378 -0
- package/tools/_actions.js +491 -22
- package/tools/_connection-queue.js +257 -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/_work-context.js +338 -0
- package/tools/_work-context.manual-test.js +199 -0
- package/tools/_work-context.test.js +260 -0
- package/tools/admin-inbox.js +218 -0
- package/tools/agent-treasury.js +288 -0
- package/tools/agents.js +122 -0
- package/tools/analytics.js +191 -0
- package/tools/approve.js +197 -0
- package/tools/arcade.js +173 -0
- package/tools/artifact-create.js +247 -0
- package/tools/artifact-view.js +174 -0
- package/tools/artifacts-price.js +107 -0
- package/tools/ask-expert.js +160 -0
- package/tools/auto-suggest-connections.js +304 -0
- package/tools/away.js +68 -0
- package/tools/back.js +51 -0
- package/tools/become-expert.js +150 -0
- package/tools/bootstrap-skills.js +231 -0
- package/tools/bridge-dashboard.js +342 -0
- package/tools/bridge-health.js +400 -0
- package/tools/bridge-live.js +384 -0
- package/tools/bridges.js +383 -0
- package/tools/broadcast.js +286 -0
- package/tools/bye.js +4 -0
- package/tools/chat.js +202 -0
- package/tools/collaborative-drawing.js +286 -0
- package/tools/colorguess.js +281 -0
- package/tools/crossword.js +369 -0
- package/tools/discover-insights.js +379 -0
- package/tools/discover-momentum.js +256 -0
- package/tools/discover.js +675 -0
- package/tools/discovery-analytics.js +345 -0
- package/tools/discovery-auto-suggest.js +275 -0
- package/tools/discovery-bootstrap.js +267 -0
- package/tools/discovery-daily.js +375 -0
- package/tools/discovery-dashboard.js +385 -0
- package/tools/discovery-digest.js +314 -0
- package/tools/discovery-hub.js +357 -0
- package/tools/discovery-insights.js +384 -0
- package/tools/discovery-momentum.js +281 -0
- package/tools/discovery-monitor.js +319 -0
- package/tools/discovery-proactive.js +300 -0
- package/tools/dm.js +84 -14
- package/tools/draw.js +317 -0
- package/tools/drawing.js +310 -0
- package/tools/earnings.js +126 -0
- package/tools/echo.js +16 -0
- package/tools/farcaster.js +307 -0
- package/tools/feed.js +215 -0
- package/tools/follow.js +224 -0
- package/tools/friends.js +192 -0
- package/tools/game.js +218 -110
- package/tools/games-catalog.js +376 -0
- package/tools/games.js +313 -0
- package/tools/genesis.js +233 -0
- package/tools/gig-browse.js +206 -0
- package/tools/gig-complete.js +139 -0
- package/tools/guessnumber.js +194 -0
- package/tools/hangman.js +129 -0
- package/tools/help.js +269 -0
- package/tools/idea.js +217 -0
- package/tools/inbox.js +291 -25
- package/tools/init.js +657 -33
- package/tools/insights.js +123 -0
- package/tools/invite.js +142 -21
- 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 +275 -0
- package/tools/multiplayer-tictactoe.js +303 -0
- package/tools/mute.js +97 -0
- package/tools/notifications.js +415 -0
- package/tools/observe.js +200 -0
- package/tools/onboarding.js +147 -0
- package/tools/open.js +52 -3
- package/tools/party-game.js +314 -0
- package/tools/plan.js +225 -0
- package/tools/presence-agent.js +167 -0
- package/tools/profile.js +219 -0
- package/tools/proof-of-work.js +139 -0
- package/tools/pulse.js +218 -0
- package/tools/react.js +4 -0
- package/tools/release.js +83 -0
- package/tools/report.js +109 -0
- package/tools/reputation.js +175 -0
- package/tools/request.js +231 -0
- package/tools/reservations.js +116 -0
- package/tools/reserve.js +111 -0
- package/tools/riddle.js +240 -0
- package/tools/run-bootstrap.js +69 -0
- package/tools/schedule.js +367 -0
- package/tools/session.js +420 -0
- package/tools/session_price.js +128 -0
- package/tools/settings.js +200 -0
- package/tools/ship.js +188 -0
- package/tools/shipback.js +326 -0
- package/tools/skills-analytics.js +349 -0
- package/tools/skills-bootstrap.js +301 -0
- package/tools/skills-dashboard.js +268 -0
- package/tools/skills-exchange.js +342 -0
- package/tools/skills.js +380 -0
- package/tools/smart-intro.js +353 -0
- package/tools/social-inbox.js +326 -69
- package/tools/social-post.js +251 -66
- package/tools/social-processor.js +445 -0
- package/tools/solo-game.js +390 -0
- package/tools/start.js +296 -81
- package/tools/status.js +53 -6
- package/tools/storybuilder.js +331 -0
- package/tools/stuck.js +297 -0
- package/tools/subscribe.js +148 -0
- package/tools/subscriptions.js +134 -0
- package/tools/suggest-tags.js +184 -0
- package/tools/tag-suggestions.js +257 -0
- package/tools/telegram-bot.js +183 -0
- package/tools/telegram-setup.js +214 -0
- package/tools/tictactoe.js +155 -0
- package/tools/tip.js +120 -0
- package/tools/token.js +103 -0
- package/tools/twentyquestions.js +143 -0
- package/tools/update.js +1 -1
- package/tools/wallet.js +127 -0
- package/tools/watch.js +157 -0
- package/tools/webhook-test.js +388 -0
- package/tools/who.js +118 -25
- package/tools/withdraw.js +145 -0
- package/tools/wordassociation.js +247 -0
- package/tools/work-summary.js +96 -0
- package/tools/workshop-buddy.js +394 -0
- package/tools/workshop.js +327 -0
- package/version.json +12 -3
- package/tools/board.js +0 -130
package/tools/who.js
CHANGED
|
@@ -12,6 +12,8 @@ const store = require('../store');
|
|
|
12
12
|
const notify = require('../notify');
|
|
13
13
|
const { formatTimeAgo, requireInit } = require('./_shared');
|
|
14
14
|
const { actions, formatActions } = require('./_actions');
|
|
15
|
+
const { enhanceUsersWithInference } = require('../intelligence/infer');
|
|
16
|
+
const { getTopSerendipity, getAllSerendipity } = require('../intelligence/serendipity');
|
|
15
17
|
|
|
16
18
|
const definition = {
|
|
17
19
|
name: 'vibe_who',
|
|
@@ -22,7 +24,7 @@ const definition = {
|
|
|
22
24
|
}
|
|
23
25
|
};
|
|
24
26
|
|
|
25
|
-
// Activity heat based on session signals
|
|
27
|
+
// Activity heat based on session signals + GitHub activity
|
|
26
28
|
function getHeat(user) {
|
|
27
29
|
const lastSeenMs = user.lastSeen;
|
|
28
30
|
const now = Date.now();
|
|
@@ -32,40 +34,68 @@ function getHeat(user) {
|
|
|
32
34
|
if (user.firstSeen) {
|
|
33
35
|
const sessionDuration = (lastSeenMs - new Date(user.firstSeen).getTime()) / 60000;
|
|
34
36
|
if (sessionDuration < 5 && minutesAgo < 2) {
|
|
35
|
-
return { icon: '✨', label: 'just joined' };
|
|
37
|
+
return { icon: '✨', label: 'just joined', inferred: false };
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
// GitHub activity signals — real commits are high-confidence
|
|
42
|
+
if (user.github?.shipping_mode === 'hot') {
|
|
43
|
+
const commits = user.github.total_commits || 0;
|
|
44
|
+
const label = commits > 0 ? `shipping code (${commits} commits)` : 'shipping code';
|
|
45
|
+
return { icon: '🔥', label, inferred: false, source: 'github' };
|
|
46
|
+
}
|
|
47
|
+
if (user.github?.shipping_mode === 'active') {
|
|
48
|
+
return { icon: '⚡', label: 'pushing commits', inferred: false, source: 'github' };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check for inferred state from smart detection
|
|
52
|
+
if (user.mood_inferred && user.mood) {
|
|
53
|
+
const inferredLabel = user.inferred_state
|
|
54
|
+
? `${user.inferred_state.replace('-', ' ')}`
|
|
55
|
+
: 'active';
|
|
56
|
+
return {
|
|
57
|
+
icon: user.mood,
|
|
58
|
+
label: inferredLabel,
|
|
59
|
+
inferred: true,
|
|
60
|
+
reason: user.mood_reason
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
39
64
|
// Explicit mood takes priority
|
|
40
65
|
if (user.mood === '🔥' || user.mood === '🚀') {
|
|
41
|
-
return { icon: '🔥', label: 'shipping' };
|
|
66
|
+
return { icon: '🔥', label: 'shipping', inferred: false };
|
|
42
67
|
}
|
|
43
68
|
if (user.mood === '🐛') {
|
|
44
|
-
return { icon: '🐛', label: 'debugging' };
|
|
69
|
+
return { icon: '🐛', label: 'debugging', inferred: false };
|
|
45
70
|
}
|
|
46
71
|
if (user.mood === '🌙') {
|
|
47
|
-
return { icon: '🌙', label: 'late night' };
|
|
72
|
+
return { icon: '🌙', label: 'late night', inferred: false };
|
|
48
73
|
}
|
|
49
74
|
if (user.mood === '🧠') {
|
|
50
|
-
return { icon: '🧠', label: 'deep work' };
|
|
75
|
+
return { icon: '🧠', label: 'deep work', inferred: false };
|
|
51
76
|
}
|
|
52
77
|
|
|
53
78
|
// Infer from builderMode
|
|
54
79
|
if (user.builderMode === 'deep-focus') {
|
|
55
|
-
return { icon: '🧠', label: 'deep focus' };
|
|
80
|
+
return { icon: '🧠', label: 'deep focus', inferred: false };
|
|
56
81
|
}
|
|
57
82
|
if (user.builderMode === 'shipping') {
|
|
58
|
-
return { icon: '🔥', label: 'shipping' };
|
|
83
|
+
return { icon: '🔥', label: 'shipping', inferred: false };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// GitHub building mode (lower activity but still coding)
|
|
87
|
+
if (user.github?.shipping_mode === 'building') {
|
|
88
|
+
return { icon: '🔨', label: 'building', inferred: false, source: 'github' };
|
|
59
89
|
}
|
|
60
90
|
|
|
61
91
|
// Default based on recency
|
|
62
92
|
if (minutesAgo < 2) {
|
|
63
|
-
return { icon: '⚡', label: 'active' };
|
|
93
|
+
return { icon: '⚡', label: 'active', inferred: false };
|
|
64
94
|
}
|
|
65
95
|
if (minutesAgo < 10) {
|
|
66
|
-
return { icon: '●', label: null };
|
|
96
|
+
return { icon: '●', label: null, inferred: false };
|
|
67
97
|
}
|
|
68
|
-
return { icon: '○', label: 'idle' };
|
|
98
|
+
return { icon: '○', label: 'idle', inferred: false };
|
|
69
99
|
}
|
|
70
100
|
|
|
71
101
|
// Format user's current activity
|
|
@@ -80,22 +110,34 @@ function formatActivity(user) {
|
|
|
80
110
|
parts.push(`(${user.branch})`);
|
|
81
111
|
}
|
|
82
112
|
|
|
83
|
-
// Error they're stuck on
|
|
113
|
+
// Error they're stuck on (highest priority - they might need help)
|
|
84
114
|
if (user.error) {
|
|
85
115
|
const shortError = user.error.slice(0, 50) + (user.error.length > 50 ? '...' : '');
|
|
86
116
|
return `⚠️ _stuck on: ${shortError}_`;
|
|
87
117
|
}
|
|
88
118
|
|
|
89
|
-
//
|
|
119
|
+
// Combine file + note if both present
|
|
120
|
+
if (user.note && parts.length > 0) {
|
|
121
|
+
return `${parts.join(' ')} — _"${user.note}"_`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Just note
|
|
90
125
|
if (user.note) {
|
|
91
126
|
return `_"${user.note}"_`;
|
|
92
127
|
}
|
|
93
128
|
|
|
94
|
-
//
|
|
129
|
+
// Just file context
|
|
95
130
|
if (parts.length > 0) {
|
|
96
131
|
return parts.join(' ');
|
|
97
132
|
}
|
|
98
133
|
|
|
134
|
+
// GitHub active repos (if no other context and GitHub connected)
|
|
135
|
+
if (user.github?.active_repos?.length > 0) {
|
|
136
|
+
const repos = user.github.active_repos.slice(0, 2);
|
|
137
|
+
const repoNames = repos.map(r => r.split('/').pop()); // Get just repo name
|
|
138
|
+
return `pushing to ${repoNames.join(', ')}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
99
141
|
// Fall back to one_liner
|
|
100
142
|
return user.one_liner || 'Building something';
|
|
101
143
|
}
|
|
@@ -104,7 +146,9 @@ async function handler(args) {
|
|
|
104
146
|
const initCheck = requireInit();
|
|
105
147
|
if (initCheck) return initCheck;
|
|
106
148
|
|
|
107
|
-
const
|
|
149
|
+
const rawUsers = await store.getActiveUsers();
|
|
150
|
+
// Apply smart detection — infer states from context signals
|
|
151
|
+
const users = enhanceUsersWithInference(rawUsers);
|
|
108
152
|
const myHandle = config.getHandle();
|
|
109
153
|
|
|
110
154
|
// Check for notifications (presence + messages)
|
|
@@ -117,7 +161,7 @@ async function handler(args) {
|
|
|
117
161
|
_You're the only one here right now..._
|
|
118
162
|
|
|
119
163
|
🎮 **Challenge someone later**: "play tictactoe with @friend"
|
|
120
|
-
💬 **
|
|
164
|
+
💬 **Message @vibe**: Say hello to the platform!
|
|
121
165
|
🔗 **Invite a friend**: Share \`slashvibe.dev\`
|
|
122
166
|
|
|
123
167
|
_Check back in a bit — builders come and go._`
|
|
@@ -138,26 +182,53 @@ _Check back in a bit — builders come and go._`
|
|
|
138
182
|
active.forEach(u => {
|
|
139
183
|
const isMe = u.handle === myHandle;
|
|
140
184
|
const tag = isMe ? ' _(you)_' : '';
|
|
185
|
+
const agentBadge = u.is_agent ? ' 🤖' : '';
|
|
186
|
+
const operatorTag = u.is_agent && u.operator ? ` _(op: @${u.operator})_` : '';
|
|
141
187
|
const heat = getHeat(u);
|
|
188
|
+
// Keep it clean — state speaks for itself
|
|
142
189
|
const heatLabel = heat.label ? ` ${heat.label}` : '';
|
|
143
190
|
const activity = formatActivity(u);
|
|
144
191
|
const timeAgo = formatTimeAgo(u.lastSeen);
|
|
145
192
|
|
|
146
|
-
display += `${heat.icon} **@${u.handle}**${tag}${heatLabel}\n`;
|
|
193
|
+
display += `${heat.icon} **@${u.handle}**${agentBadge}${tag}${heatLabel}\n`;
|
|
194
|
+
if (operatorTag) {
|
|
195
|
+
display += ` ${operatorTag}\n`;
|
|
196
|
+
}
|
|
147
197
|
display += ` ${activity}\n`;
|
|
148
198
|
display += ` _${timeAgo}_\n\n`;
|
|
149
199
|
});
|
|
150
200
|
}
|
|
151
201
|
|
|
152
|
-
// Away section (
|
|
202
|
+
// Away section (expanded with messages if present)
|
|
153
203
|
if (away.length > 0) {
|
|
154
|
-
display += `---\n`;
|
|
155
|
-
display += `**Away
|
|
156
|
-
|
|
204
|
+
display += `---\n\n`;
|
|
205
|
+
display += `**Away:**\n`;
|
|
206
|
+
|
|
207
|
+
// Split into users with away messages and without
|
|
208
|
+
const withMessage = away.filter(u => u.awayMessage);
|
|
209
|
+
const withoutMessage = away.filter(u => !u.awayMessage);
|
|
210
|
+
|
|
211
|
+
// Show users with custom away messages (expanded)
|
|
212
|
+
withMessage.forEach(u => {
|
|
157
213
|
const isMe = u.handle === myHandle;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
214
|
+
const tag = isMe ? ' _(you)_' : '';
|
|
215
|
+
const timeAgo = formatTimeAgo(u.lastSeen);
|
|
216
|
+
|
|
217
|
+
display += `☕ **@${u.handle}**${tag} — _"${u.awayMessage}"_\n`;
|
|
218
|
+
display += ` _${timeAgo}_\n\n`;
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Show auto-away users (collapsed) with 💤
|
|
222
|
+
if (withoutMessage.length > 0) {
|
|
223
|
+
withoutMessage.forEach(u => {
|
|
224
|
+
const isMe = u.handle === myHandle;
|
|
225
|
+
const tag = isMe ? ' _(you)_' : '';
|
|
226
|
+
const timeAgo = formatTimeAgo(u.lastSeen);
|
|
227
|
+
|
|
228
|
+
display += `💤 **@${u.handle}**${tag} _(auto-away)_\n`;
|
|
229
|
+
display += ` _${timeAgo}_\n\n`;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
161
232
|
}
|
|
162
233
|
|
|
163
234
|
// Fun quick actions - randomize suggestions
|
|
@@ -176,7 +247,7 @@ _Check back in a bit — builders come and go._`
|
|
|
176
247
|
try {
|
|
177
248
|
const unread = await store.getUnreadCount(myHandle);
|
|
178
249
|
if (unread > 0) {
|
|
179
|
-
display += `\n\n📬
|
|
250
|
+
display += `\n\n📬 **NEW MESSAGE — ${unread} UNREAD** — \`vibe inbox\``;
|
|
180
251
|
}
|
|
181
252
|
} catch (e) {}
|
|
182
253
|
|
|
@@ -185,6 +256,18 @@ _Check back in a bit — builders come and go._`
|
|
|
185
256
|
display += `\n\n_The room is lively today!_ ⚡`;
|
|
186
257
|
}
|
|
187
258
|
|
|
259
|
+
// Genesis spots remaining
|
|
260
|
+
try {
|
|
261
|
+
const stats = await store.getStats();
|
|
262
|
+
if (stats.genesis && stats.genesis.genesis_remaining > 0) {
|
|
263
|
+
display += `\n\n🌱 **${stats.genesis.genesis_remaining} genesis spots left** of ${stats.genesis.genesis_cap}`;
|
|
264
|
+
} else if (stats.genesis && stats.genesis.genesis_remaining === 0) {
|
|
265
|
+
display += `\n\n_Genesis is full — ${stats.genesis.total} builders strong_`;
|
|
266
|
+
}
|
|
267
|
+
} catch (e) {
|
|
268
|
+
// Silent fail — genesis display is nice-to-have
|
|
269
|
+
}
|
|
270
|
+
|
|
188
271
|
// Build response with optional hints for structured flows
|
|
189
272
|
const response = { display };
|
|
190
273
|
|
|
@@ -233,6 +316,16 @@ _Check back in a bit — builders come and go._`
|
|
|
233
316
|
response.suggestion = topSuggestion;
|
|
234
317
|
}
|
|
235
318
|
|
|
319
|
+
// Serendipity detection — quiet awareness, not loud callouts
|
|
320
|
+
const myUser = users.find(u => u.handle === myHandle);
|
|
321
|
+
if (myUser && active.length > 1) {
|
|
322
|
+
const serendipity = getTopSerendipity(myUser, active);
|
|
323
|
+
if (serendipity && serendipity.relevance > 0.75) {
|
|
324
|
+
// Only surface high-confidence matches, and quietly
|
|
325
|
+
response.serendipity = serendipity;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
236
329
|
// Add guided mode actions
|
|
237
330
|
const onlineHandles = active.filter(u => u.handle !== myHandle).map(u => u.handle);
|
|
238
331
|
const unreadCount = await store.getUnreadCount(myHandle).catch(() => 0);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe_withdraw - Withdraw earnings from FeeRouter
|
|
3
|
+
*
|
|
4
|
+
* Generates transaction data to withdraw accumulated on-chain earnings.
|
|
5
|
+
* User must sign and submit the transaction with their wallet.
|
|
6
|
+
*
|
|
7
|
+
* Examples:
|
|
8
|
+
* - "vibe withdraw"
|
|
9
|
+
* - "withdraw my earnings"
|
|
10
|
+
* - "claim my on-chain balance"
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fetch = require('node-fetch');
|
|
14
|
+
const config = require('../config');
|
|
15
|
+
|
|
16
|
+
const definition = {
|
|
17
|
+
name: 'vibe_withdraw',
|
|
18
|
+
description: 'Withdraw accumulated earnings from the FeeRouter. Generates transaction data for you to sign.',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
to_address: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'Optional destination address (defaults to your registered payout wallet)'
|
|
25
|
+
},
|
|
26
|
+
check_only: {
|
|
27
|
+
type: 'boolean',
|
|
28
|
+
description: 'Only check balance without generating withdrawal transaction'
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
async function handler(args) {
|
|
35
|
+
const { to_address, check_only = false } = args;
|
|
36
|
+
|
|
37
|
+
if (!config.isInitialized()) {
|
|
38
|
+
return {
|
|
39
|
+
display: 'Run `vibe init` first to set your identity.'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const handle = config.getHandle();
|
|
44
|
+
const token = config.getToken();
|
|
45
|
+
const apiUrl = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// Check-only mode
|
|
49
|
+
if (check_only) {
|
|
50
|
+
const response = await fetch(
|
|
51
|
+
`${apiUrl}/api/earnings/withdraw?handle=${encodeURIComponent(handle)}`,
|
|
52
|
+
{
|
|
53
|
+
headers: token ? {
|
|
54
|
+
'Authorization': `Bearer ${token}`
|
|
55
|
+
} : {}
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const result = await response.json();
|
|
60
|
+
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
return {
|
|
63
|
+
display: `❌ ${result.error || 'Failed to check balance'}`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const statusEmoji = result.can_withdraw ? '🟢' : '🟡';
|
|
68
|
+
const action = result.can_withdraw
|
|
69
|
+
? 'Run "vibe withdraw" to generate withdrawal transaction.'
|
|
70
|
+
: result.registered
|
|
71
|
+
? 'No balance to withdraw yet.'
|
|
72
|
+
: 'Register on-chain first: vibe register_onchain';
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
display: `
|
|
76
|
+
💰 Withdrawal Status: @${handle}
|
|
77
|
+
|
|
78
|
+
Balance: ${result.balance_eth} ETH
|
|
79
|
+
Registered: ${result.registered ? 'Yes' : 'No'}
|
|
80
|
+
Can Withdraw: ${statusEmoji} ${result.can_withdraw ? 'Yes' : 'No'}
|
|
81
|
+
|
|
82
|
+
${action}
|
|
83
|
+
`.trim(),
|
|
84
|
+
data: result
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Generate withdrawal transaction
|
|
89
|
+
const response = await fetch(
|
|
90
|
+
`${apiUrl}/api/earnings/withdraw`,
|
|
91
|
+
{
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers: {
|
|
94
|
+
'Content-Type': 'application/json',
|
|
95
|
+
'Authorization': `Bearer ${token}`
|
|
96
|
+
},
|
|
97
|
+
body: JSON.stringify({
|
|
98
|
+
to_address: to_address || undefined
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const result = await response.json();
|
|
104
|
+
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
let display = `❌ ${result.error || 'Failed to generate withdrawal'}`;
|
|
107
|
+
if (result.action) {
|
|
108
|
+
display += `\n\nNext step: ${result.action}`;
|
|
109
|
+
}
|
|
110
|
+
return { display };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const destDisplay = result.destination
|
|
114
|
+
? `${result.destination.slice(0, 10)}...${result.destination.slice(-8)}`
|
|
115
|
+
: 'your wallet';
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
display: `
|
|
119
|
+
💸 Withdrawal Ready: @${handle}
|
|
120
|
+
|
|
121
|
+
Amount: ${result.balance_display}
|
|
122
|
+
Destination: ${destDisplay}
|
|
123
|
+
Network: ${result.network}
|
|
124
|
+
|
|
125
|
+
Transaction to sign:
|
|
126
|
+
To: ${result.transaction.to}
|
|
127
|
+
Data: ${result.transaction.data?.slice(0, 40)}...
|
|
128
|
+
Gas: ${result.transaction.gas}
|
|
129
|
+
|
|
130
|
+
Instructions:
|
|
131
|
+
${result.instructions?.map((i, idx) => ` ${idx + 1}. ${i}`).join('\n')}
|
|
132
|
+
|
|
133
|
+
Explorer: ${result.explorer}
|
|
134
|
+
`.trim(),
|
|
135
|
+
data: result
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
} catch (error) {
|
|
139
|
+
return {
|
|
140
|
+
display: `❌ Failed to process withdrawal: ${error.message}`
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = { definition, handler };
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe wordassociation — Start or play word association games
|
|
3
|
+
*
|
|
4
|
+
* A multiplayer game where players take turns saying words that associate with the previous word.
|
|
5
|
+
* Build creative chains of connected ideas!
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const config = require('../config');
|
|
9
|
+
const store = require('../store');
|
|
10
|
+
const { createGamePayload } = require('../protocol');
|
|
11
|
+
const { requireInit, normalizeHandle } = require('./_shared');
|
|
12
|
+
|
|
13
|
+
// Word association game implementation
|
|
14
|
+
const wordassociation = require('../games/wordassociation');
|
|
15
|
+
|
|
16
|
+
const definition = {
|
|
17
|
+
name: 'vibe_wordassociation',
|
|
18
|
+
description: 'Start or play word association games. Players take turns saying related words.',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
word: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'Word to add to the association chain'
|
|
25
|
+
},
|
|
26
|
+
join: {
|
|
27
|
+
type: 'boolean',
|
|
28
|
+
description: 'Join an existing game'
|
|
29
|
+
},
|
|
30
|
+
new: {
|
|
31
|
+
type: 'boolean',
|
|
32
|
+
description: 'Start a new game'
|
|
33
|
+
},
|
|
34
|
+
invite: {
|
|
35
|
+
type: 'string',
|
|
36
|
+
description: 'Invite someone to the game (e.g., @username)'
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
required: []
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Find the most recent word association game state in DMs or group context
|
|
45
|
+
*/
|
|
46
|
+
async function getGameState(myHandle) {
|
|
47
|
+
// For now, we'll store game state in a special "game room" thread with myself
|
|
48
|
+
// This allows for multiplayer games that persist across sessions
|
|
49
|
+
const gameRoomHandle = 'wordassociation-room';
|
|
50
|
+
const thread = await store.getThread(myHandle, gameRoomHandle);
|
|
51
|
+
|
|
52
|
+
// Find the most recent game payload
|
|
53
|
+
for (let i = thread.length - 1; i >= 0; i--) {
|
|
54
|
+
const msg = thread[i];
|
|
55
|
+
if (msg.payload?.type === 'game' && msg.payload?.game === 'wordassociation') {
|
|
56
|
+
return { gameState: msg.payload.state, thread };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { gameState: null, thread };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Save game state to the game room
|
|
65
|
+
*/
|
|
66
|
+
async function saveGameState(myHandle, gameState, action) {
|
|
67
|
+
const gameRoomHandle = 'wordassociation-room';
|
|
68
|
+
const payload = createGamePayload('wordassociation', gameState);
|
|
69
|
+
|
|
70
|
+
let message;
|
|
71
|
+
if (action.type === 'new') {
|
|
72
|
+
message = 'Started a new word association game!';
|
|
73
|
+
} else if (action.type === 'join') {
|
|
74
|
+
message = `@${action.player} joined the game!`;
|
|
75
|
+
} else if (action.type === 'word') {
|
|
76
|
+
message = `@${action.player} said "${action.word}"`;
|
|
77
|
+
if (gameState.gameOver) {
|
|
78
|
+
message += ' - Game complete! 🎉';
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
message = 'Game updated';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await store.sendMessage(myHandle, gameRoomHandle, message, 'dm', payload);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Invite someone to play
|
|
89
|
+
*/
|
|
90
|
+
async function invitePlayer(myHandle, targetHandle) {
|
|
91
|
+
const them = normalizeHandle(targetHandle);
|
|
92
|
+
const message = `Hey! I started a word association game. Want to join? Use \`vibe wordassociation --join\` to play!`;
|
|
93
|
+
await store.sendMessage(myHandle, them, message, 'dm');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function handler(args) {
|
|
97
|
+
const initCheck = requireInit();
|
|
98
|
+
if (initCheck) return initCheck;
|
|
99
|
+
|
|
100
|
+
const { word, join, new: newGame, invite } = args;
|
|
101
|
+
const myHandle = config.getHandle();
|
|
102
|
+
|
|
103
|
+
// Get current game state
|
|
104
|
+
const { gameState, thread } = await getGameState(myHandle);
|
|
105
|
+
|
|
106
|
+
// Handle inviting someone
|
|
107
|
+
if (invite) {
|
|
108
|
+
await invitePlayer(myHandle, invite);
|
|
109
|
+
return {
|
|
110
|
+
display: `Invited @${normalizeHandle(invite)} to join the word association game! 🧠`
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Handle starting a new game
|
|
115
|
+
if (newGame) {
|
|
116
|
+
const newState = wordassociation.createInitialWordAssociationState();
|
|
117
|
+
const addResult = wordassociation.addPlayer(newState, myHandle);
|
|
118
|
+
|
|
119
|
+
if (addResult.error) {
|
|
120
|
+
return { display: addResult.error };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
await saveGameState(myHandle, addResult.gameState, { type: 'new', player: myHandle });
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
display: `🧠 **New Word Association Game!**\n\n${wordassociation.formatWordAssociationDisplay(addResult.gameState)}\n\nUse \`vibe wordassociation --word [word]\` to start the chain!\nInvite friends with \`vibe wordassociation --invite @username\``
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Handle joining existing game
|
|
131
|
+
if (join) {
|
|
132
|
+
if (!gameState) {
|
|
133
|
+
// Start a new game if none exists
|
|
134
|
+
const newState = wordassociation.createInitialWordAssociationState();
|
|
135
|
+
const addResult = wordassociation.addPlayer(newState, myHandle);
|
|
136
|
+
|
|
137
|
+
if (addResult.error) {
|
|
138
|
+
return { display: addResult.error };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
await saveGameState(myHandle, addResult.gameState, { type: 'new', player: myHandle });
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
display: `🧠 **Joined Word Association Game!**\n\n${wordassociation.formatWordAssociationDisplay(addResult.gameState)}\n\nUse \`vibe wordassociation --word [word]\` to start the chain!`
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (gameState.gameOver) {
|
|
149
|
+
return { display: 'The current game is over. Use `vibe wordassociation --new` to start a fresh game!' };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const addResult = wordassociation.addPlayer(gameState, myHandle);
|
|
153
|
+
|
|
154
|
+
if (addResult.error) {
|
|
155
|
+
return { display: addResult.error };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
await saveGameState(myHandle, addResult.gameState, { type: 'join', player: myHandle });
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
display: `🧠 **Joined Word Association Game!**\n\n${wordassociation.formatWordAssociationDisplay(addResult.gameState)}`
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Handle making a word move
|
|
166
|
+
if (word) {
|
|
167
|
+
if (!gameState) {
|
|
168
|
+
return { display: 'No active game! Use `vibe wordassociation --new` to start one, or `vibe wordassociation --join` to join.' };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (gameState.gameOver) {
|
|
172
|
+
return { display: 'Game is over! Use `vibe wordassociation --new` to start a fresh game.' };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Try to make the move
|
|
176
|
+
const moveResult = wordassociation.makeMove(gameState, word, myHandle);
|
|
177
|
+
|
|
178
|
+
if (moveResult.error) {
|
|
179
|
+
return { display: `❌ ${moveResult.error}` };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
await saveGameState(myHandle, moveResult.gameState, {
|
|
183
|
+
type: 'word',
|
|
184
|
+
player: myHandle,
|
|
185
|
+
word: word
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const display = wordassociation.formatWordAssociationDisplay(moveResult.gameState);
|
|
189
|
+
|
|
190
|
+
// Add fun themes if game is complete
|
|
191
|
+
if (moveResult.gameState.gameOver) {
|
|
192
|
+
const themes = wordassociation.findThemes(moveResult.gameState);
|
|
193
|
+
const stats = wordassociation.getGameStats(moveResult.gameState);
|
|
194
|
+
|
|
195
|
+
let extra = '\n\n**Final Stats:**\n';
|
|
196
|
+
if (stats) {
|
|
197
|
+
extra += `Total words: ${stats.totalWords}\n`;
|
|
198
|
+
extra += `Players: ${stats.contributors}\n`;
|
|
199
|
+
if (themes.length > 0) {
|
|
200
|
+
extra += `\n**Themes found:** ${themes.join(', ')}`;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
display: `🧠 **Word Association**\n\n${display}${extra}\n\nStart a new game with \`vibe wordassociation --new\`!`
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
display: `🧠 **Word Association**\n\n${display}`
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Show current game state
|
|
215
|
+
if (gameState) {
|
|
216
|
+
const display = wordassociation.formatWordAssociationDisplay(gameState);
|
|
217
|
+
|
|
218
|
+
if (gameState.gameOver) {
|
|
219
|
+
const themes = wordassociation.findThemes(gameState);
|
|
220
|
+
const stats = wordassociation.getGameStats(gameState);
|
|
221
|
+
|
|
222
|
+
let extra = '\n\n**Final Stats:**\n';
|
|
223
|
+
if (stats) {
|
|
224
|
+
extra += `Total words: ${stats.totalWords}\n`;
|
|
225
|
+
extra += `Players: ${stats.contributors}\n`;
|
|
226
|
+
if (themes.length > 0) {
|
|
227
|
+
extra += `\n**Themes found:** ${themes.join(', ')}`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
display: `🧠 **Word Association**\n\n${display}${extra}\n\nStart a new game with \`vibe wordassociation --new\`!`
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
display: `🧠 **Word Association**\n\n${display}\n\nUse \`vibe wordassociation --word [word]\` to continue!\nInvite friends: \`vibe wordassociation --invite @username\``
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// No active game
|
|
242
|
+
return {
|
|
243
|
+
display: `🧠 **Word Association**\n\nNo active game found.\n\n**Commands:**\n• \`vibe wordassociation --new\` - Start a new game\n• \`vibe wordassociation --join\` - Join existing game\n• \`vibe wordassociation --word [word]\` - Add a word\n• \`vibe wordassociation --invite @user\` - Invite someone\n\n**How to play:**\nPlayers take turns saying words that associate with the previous word. Let your creativity flow and see what interesting chains emerge!`
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
module.exports = { definition, handler };
|