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.
Files changed (205) hide show
  1. package/README.md +1 -0
  2. package/analytics.js +107 -0
  3. package/auth-store.js +148 -0
  4. package/auto-update.js +130 -0
  5. package/bridges/bridge-monitor.js +388 -0
  6. package/bridges/discord-bot.js +431 -0
  7. package/bridges/farcaster.js +299 -0
  8. package/bridges/telegram.js +261 -0
  9. package/bridges/webhook-health.js +420 -0
  10. package/bridges/webhook-server.js +437 -0
  11. package/bridges/whatsapp.js +441 -0
  12. package/bridges/x-webhook.js +423 -0
  13. package/config.js +154 -5
  14. package/crypto.js +3 -8
  15. package/games/arcade.js +406 -0
  16. package/games/chess.js +451 -0
  17. package/games/colorguess.js +343 -0
  18. package/games/crossword-words.js +171 -0
  19. package/games/crossword.js +461 -0
  20. package/games/drawing.js +347 -0
  21. package/games/gameroulette.js +300 -0
  22. package/games/gamerouter.js +336 -0
  23. package/games/gamestatus.js +337 -0
  24. package/games/guessnumber.js +209 -0
  25. package/games/hangman.js +279 -0
  26. package/games/memory.js +338 -0
  27. package/games/multiplayer-tictactoe.js +389 -0
  28. package/games/pixelart.js +399 -0
  29. package/games/quickduel.js +354 -0
  30. package/games/riddle.js +371 -0
  31. package/games/rockpaperscissors.js +291 -0
  32. package/games/snake.js +406 -0
  33. package/games/storybuilder.js +343 -0
  34. package/games/tictactoe.js +345 -0
  35. package/games/twentyquestions.js +286 -0
  36. package/games/twotruths.js +207 -0
  37. package/games/werewolf.js +508 -0
  38. package/games/wordassociation.js +247 -0
  39. package/games/wordchain.js +135 -0
  40. package/index.js +105 -216
  41. package/intelligence/index.js +45 -0
  42. package/intelligence/infer.js +316 -0
  43. package/intelligence/interests.js +369 -0
  44. package/intelligence/patterns.js +651 -0
  45. package/intelligence/proactive.js +358 -0
  46. package/intelligence/serendipity.js +306 -0
  47. package/memory.js +166 -0
  48. package/notification-emitter.js +77 -0
  49. package/notify.js +102 -1
  50. package/package.json +21 -7
  51. package/prompts.js +1 -1
  52. package/protocol/index.js +161 -1
  53. package/setup.js +402 -0
  54. package/store/api.js +528 -82
  55. package/store/profiles.js +160 -12
  56. package/tools/_actions.js +463 -16
  57. package/tools/_deprecated/auto-suggest-connections.js +304 -0
  58. package/tools/_deprecated/bootstrap-skills.js +231 -0
  59. package/tools/_deprecated/bridge-dashboard.js +342 -0
  60. package/tools/_deprecated/bridge-health.js +400 -0
  61. package/tools/_deprecated/bridge-live.js +384 -0
  62. package/tools/_deprecated/bridges.js +383 -0
  63. package/tools/_deprecated/colorguess.js +281 -0
  64. package/tools/_deprecated/discover-insights.js +379 -0
  65. package/tools/_deprecated/discover-momentum.js +256 -0
  66. package/tools/_deprecated/discovery-analytics.js +345 -0
  67. package/tools/_deprecated/discovery-auto-suggest.js +275 -0
  68. package/tools/_deprecated/discovery-bootstrap.js +267 -0
  69. package/tools/_deprecated/discovery-daily.js +375 -0
  70. package/tools/_deprecated/discovery-dashboard.js +385 -0
  71. package/tools/_deprecated/discovery-digest.js +314 -0
  72. package/tools/_deprecated/discovery-hub.js +357 -0
  73. package/tools/_deprecated/discovery-insights.js +384 -0
  74. package/tools/_deprecated/discovery-momentum.js +281 -0
  75. package/tools/_deprecated/discovery-monitor.js +319 -0
  76. package/tools/_deprecated/discovery-proactive.js +300 -0
  77. package/tools/_deprecated/draw.js +317 -0
  78. package/tools/_deprecated/farcaster.js +307 -0
  79. package/tools/_deprecated/forget.js +119 -0
  80. package/tools/_deprecated/games-catalog.js +376 -0
  81. package/tools/_deprecated/games.js +313 -0
  82. package/tools/_deprecated/guessnumber.js +194 -0
  83. package/tools/_deprecated/hangman.js +129 -0
  84. package/tools/_deprecated/multiplayer-tictactoe.js +303 -0
  85. package/tools/_deprecated/recall.js +147 -0
  86. package/tools/_deprecated/remember.js +86 -0
  87. package/tools/_deprecated/riddle.js +240 -0
  88. package/tools/_deprecated/run-bootstrap.js +69 -0
  89. package/tools/_deprecated/skills-analytics.js +349 -0
  90. package/tools/_deprecated/skills-bootstrap.js +301 -0
  91. package/tools/_deprecated/skills-dashboard.js +268 -0
  92. package/tools/_deprecated/skills.js +380 -0
  93. package/tools/_deprecated/smart-intro.js +353 -0
  94. package/tools/_deprecated/storybuilder.js +331 -0
  95. package/tools/_deprecated/telegram-bot.js +183 -0
  96. package/tools/_deprecated/telegram-setup.js +214 -0
  97. package/tools/_deprecated/twentyquestions.js +143 -0
  98. package/tools/_discovery-enhanced.js +290 -0
  99. package/tools/_discovery.js +439 -0
  100. package/tools/_proactive-discovery.js +301 -0
  101. package/tools/_shared/index.js +64 -0
  102. package/tools/_shared.js +234 -0
  103. package/tools/_work-context.js +338 -0
  104. package/tools/_work-context.manual-test.js +199 -0
  105. package/tools/_work-context.test.js +260 -0
  106. package/tools/activity.js +220 -0
  107. package/tools/admin-inbox.js +218 -0
  108. package/tools/agent-treasury.js +288 -0
  109. package/tools/analytics.js +191 -0
  110. package/tools/approve.js +197 -0
  111. package/tools/arcade.js +173 -0
  112. package/tools/artifact-create.js +17 -11
  113. package/tools/artifact-view.js +31 -1
  114. package/tools/artifacts-price.js +47 -43
  115. package/tools/ask-expert.js +160 -0
  116. package/tools/available.js +120 -0
  117. package/tools/become-expert.js +150 -0
  118. package/tools/broadcast.js +286 -0
  119. package/tools/chat.js +202 -0
  120. package/tools/collaborative-drawing.js +286 -0
  121. package/tools/connection-status.js +178 -0
  122. package/tools/crossword.js +17 -1
  123. package/tools/discover.js +350 -34
  124. package/tools/dm.js +115 -73
  125. package/tools/drawing.js +1 -1
  126. package/tools/earnings.js +126 -0
  127. package/tools/echo.js +16 -0
  128. package/tools/feed.js +51 -7
  129. package/tools/follow.js +224 -0
  130. package/tools/friends.js +207 -0
  131. package/tools/game.js +2 -2
  132. package/tools/genesis.js +233 -0
  133. package/tools/gig-browse.js +206 -0
  134. package/tools/gig-complete.js +144 -0
  135. package/tools/handoff.js +7 -1
  136. package/tools/help.js +4 -4
  137. package/tools/idea.js +9 -2
  138. package/tools/inbox.js +334 -28
  139. package/tools/init.js +727 -36
  140. package/tools/invite.js +15 -4
  141. package/tools/l2-bridge.js +272 -0
  142. package/tools/l2-status.js +217 -0
  143. package/tools/l2.js +206 -0
  144. package/tools/migrate.js +156 -0
  145. package/tools/mint.js +377 -0
  146. package/tools/multiplayer-game.js +1 -1
  147. package/tools/notifications.js +415 -0
  148. package/tools/observe.js +200 -0
  149. package/tools/onboarding.js +147 -0
  150. package/tools/open.js +143 -12
  151. package/tools/party-game.js +2 -2
  152. package/tools/plan.js +225 -0
  153. package/tools/presence-agent.js +7 -0
  154. package/tools/proof-of-work.js +100 -104
  155. package/tools/pulse.js +218 -0
  156. package/tools/reply.js +166 -0
  157. package/tools/report.js +2 -2
  158. package/tools/reputation.js +175 -0
  159. package/tools/request.js +17 -3
  160. package/tools/schedule.js +367 -0
  161. package/tools/search-messages.js +123 -0
  162. package/tools/session.js +420 -0
  163. package/tools/session_price.js +128 -0
  164. package/tools/settings.js +126 -3
  165. package/tools/ship.js +31 -8
  166. package/tools/shipback.js +326 -0
  167. package/tools/smart-check.js +201 -0
  168. package/tools/social-processor.js +445 -0
  169. package/tools/solo-game.js +1 -1
  170. package/tools/start.js +335 -93
  171. package/tools/status.js +53 -6
  172. package/tools/stuck.js +297 -0
  173. package/tools/subscribe.js +148 -0
  174. package/tools/subscriptions.js +134 -0
  175. package/tools/suggest-tags.js +6 -8
  176. package/tools/tag-suggestions.js +257 -0
  177. package/tools/tip.js +193 -0
  178. package/tools/token.js +103 -0
  179. package/tools/update.js +1 -1
  180. package/tools/wallet.js +239 -186
  181. package/tools/watch.js +157 -0
  182. package/tools/webhook-test.js +388 -0
  183. package/tools/who.js +54 -3
  184. package/tools/withdraw.js +145 -0
  185. package/tools/work-summary.js +96 -0
  186. package/tools/workshop.js +327 -0
  187. package/tools/x-mentions.js +1 -1
  188. package/version.json +14 -3
  189. package/tools/artifacts-buy.js +0 -111
  190. package/tools/connect.js +0 -284
  191. package/tools/gigs-apply.js +0 -99
  192. package/tools/gigs-browse.js +0 -114
  193. package/tools/gigs-complete.js +0 -128
  194. package/tools/gigs-post.js +0 -140
  195. package/tools/live-off.js +0 -109
  196. package/tools/live-watch.js +0 -176
  197. package/tools/live.js +0 -128
  198. package/tools/sessions-browse.js +0 -105
  199. package/tools/whats-happening.js +0 -125
  200. /package/tools/{away.js → _deprecated/away.js} +0 -0
  201. /package/tools/{back.js → _deprecated/back.js} +0 -0
  202. /package/tools/{mute.js → _deprecated/mute.js} +0 -0
  203. /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
  204. /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
  205. /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
@@ -0,0 +1,388 @@
1
+ /**
2
+ * vibe webhook-test — Test webhook endpoints
3
+ *
4
+ * Send test events to webhook endpoints to verify they're working.
5
+ * Useful for debugging X, Telegram, Discord webhook integrations.
6
+ */
7
+
8
+ const { requireInit, header, divider, success, warning, error } = require('./_shared');
9
+
10
+ const definition = {
11
+ name: 'vibe_webhook_test',
12
+ description: 'Test webhook endpoints and view webhook activity',
13
+ inputSchema: {
14
+ type: 'object',
15
+ properties: {
16
+ endpoint: {
17
+ type: 'string',
18
+ enum: ['x', 'telegram', 'discord', 'all'],
19
+ description: 'Which webhook to test (default: all)'
20
+ },
21
+ action: {
22
+ type: 'string',
23
+ enum: ['test', 'status', 'stats', 'events'],
24
+ description: 'Test webhook, check status, view stats, or list recent events'
25
+ },
26
+ mock_event: {
27
+ type: 'string',
28
+ enum: ['mention', 'dm', 'like', 'follow'],
29
+ description: 'Type of mock event to send (for testing)'
30
+ },
31
+ limit: {
32
+ type: 'number',
33
+ description: 'Number of recent events to show (default: 10)'
34
+ }
35
+ }
36
+ }
37
+ };
38
+
39
+ async function handler(args) {
40
+ const initCheck = requireInit();
41
+ if (initCheck) return initCheck;
42
+
43
+ const {
44
+ endpoint = 'all',
45
+ action = 'status',
46
+ mock_event = 'mention',
47
+ limit = 10
48
+ } = args;
49
+
50
+ try {
51
+ switch (action) {
52
+ case 'test':
53
+ return await testWebhook(endpoint, mock_event);
54
+ case 'status':
55
+ return await checkWebhookStatus(endpoint);
56
+ case 'stats':
57
+ return await getWebhookStats(endpoint);
58
+ case 'events':
59
+ return await getRecentEvents(endpoint, limit);
60
+ default:
61
+ return { display: error('Invalid action') };
62
+ }
63
+ } catch (e) {
64
+ return {
65
+ display: `${header('Webhook Test')}\n\n${error('Test failed: ' + e.message)}`
66
+ };
67
+ }
68
+ }
69
+
70
+ async function testWebhook(endpoint, mockEvent) {
71
+ let display = header('Webhook Test');
72
+ display += '\n\n';
73
+
74
+ if (endpoint === 'x' || endpoint === 'all') {
75
+ display += await testXWebhook(mockEvent);
76
+ display += '\n';
77
+ }
78
+
79
+ if (endpoint === 'telegram' || endpoint === 'all') {
80
+ display += '**Telegram Webhook:** Not yet implemented\n\n';
81
+ }
82
+
83
+ if (endpoint === 'discord' || endpoint === 'all') {
84
+ display += '**Discord Webhook:** Not yet implemented\n\n';
85
+ }
86
+
87
+ display += divider();
88
+ display += '**Next steps:**\n';
89
+ display += '• Check webhook events: `vibe webhook-test --action events`\n';
90
+ display += '• View social inbox: `vibe social-inbox --include_webhooks`\n';
91
+ display += '• Monitor bridge health: `vibe bridge-health`';
92
+
93
+ return { display };
94
+ }
95
+
96
+ async function testXWebhook(mockEvent) {
97
+ let result = '**X (Twitter) Webhook Test**\n';
98
+
99
+ // Check if endpoint exists
100
+ const webhookUrl = '/api/webhooks/x';
101
+ result += `Endpoint: ${webhookUrl}\n`;
102
+
103
+ // Check configuration
104
+ const secret = process.env.X_WEBHOOK_SECRET;
105
+ const configured = !!secret;
106
+ result += `Secret configured: ${configured ? '✅' : '❌'}\n`;
107
+
108
+ if (!configured) {
109
+ result += warning('Set X_WEBHOOK_SECRET environment variable\n');
110
+ return result;
111
+ }
112
+
113
+ // Create mock event
114
+ const mockEvents = {
115
+ mention: {
116
+ tweet_create_events: [{
117
+ id_str: 'test_' + Date.now(),
118
+ text: '@yourusername This is a test mention from webhook-test',
119
+ created_at: new Date().toISOString(),
120
+ user: {
121
+ id_str: 'test_user_123',
122
+ screen_name: 'testuser',
123
+ name: 'Test User',
124
+ profile_image_url_https: 'https://example.com/avatar.jpg'
125
+ },
126
+ entities: {
127
+ user_mentions: [{
128
+ id_str: 'your_user_id',
129
+ screen_name: 'yourusername'
130
+ }]
131
+ }
132
+ }]
133
+ },
134
+ dm: {
135
+ direct_message_events: [{
136
+ id: 'test_dm_' + Date.now(),
137
+ created_timestamp: Date.now().toString(),
138
+ message_create: {
139
+ sender_id: 'test_user_123',
140
+ target: { recipient_id: 'your_user_id' },
141
+ message_data: { text: 'Test DM from webhook-test tool' }
142
+ }
143
+ }]
144
+ },
145
+ like: {
146
+ favorite_events: [{
147
+ id_str: 'test_like_' + Date.now(),
148
+ created_at: new Date().toISOString(),
149
+ user: {
150
+ id_str: 'test_user_123',
151
+ screen_name: 'testuser',
152
+ name: 'Test User',
153
+ profile_image_url_https: 'https://example.com/avatar.jpg'
154
+ },
155
+ favorited_status: {
156
+ id_str: 'your_tweet_123',
157
+ text: 'Your tweet that got liked',
158
+ user: { id_str: 'your_user_id' }
159
+ }
160
+ }]
161
+ },
162
+ follow: {
163
+ follow_events: [{
164
+ source: {
165
+ id_str: 'test_user_123',
166
+ screen_name: 'testuser',
167
+ name: 'Test User',
168
+ profile_image_url_https: 'https://example.com/avatar.jpg',
169
+ description: 'A test user following you'
170
+ },
171
+ target: { id_str: 'your_user_id' },
172
+ created_at: new Date().toISOString()
173
+ }]
174
+ }
175
+ };
176
+
177
+ const testPayload = mockEvents[mockEvent] || mockEvents.mention;
178
+ result += `Mock event: ${mockEvent}\n`;
179
+
180
+ // Generate test signature
181
+ const crypto = require('crypto');
182
+ const body = JSON.stringify(testPayload);
183
+ const signature = crypto
184
+ .createHmac('sha256', secret)
185
+ .update(body)
186
+ .digest('base64');
187
+
188
+ result += `Event size: ${body.length} bytes\n`;
189
+ result += success('Mock event ready for webhook\n');
190
+
191
+ // Note: We don't actually send the request to avoid spamming
192
+ // In a real implementation, this could make an HTTP request to localhost
193
+ result += warning('Note: This creates a mock event structure.\n');
194
+ result += 'To test fully, send POST to /api/webhooks/x with:\n';
195
+ result += `• Header: x-twitter-webhooks-signature: sha256=${signature}\n`;
196
+ result += `• Body: ${body.slice(0, 100)}...\n`;
197
+
198
+ return result;
199
+ }
200
+
201
+ async function checkWebhookStatus(endpoint) {
202
+ let display = header('Webhook Status');
203
+ display += '\n\n';
204
+
205
+ // X webhook status
206
+ if (endpoint === 'x' || endpoint === 'all') {
207
+ const xSecret = process.env.X_WEBHOOK_SECRET;
208
+ const xBearer = process.env.X_BEARER_TOKEN;
209
+
210
+ display += '**X (Twitter) Webhook**\n';
211
+ display += `Endpoint: /api/webhooks/x\n`;
212
+ display += `Secret: ${xSecret ? '✅ Configured' : '❌ Missing'}\n`;
213
+ display += `Bearer Token: ${xBearer ? '✅ Configured' : '❌ Missing'}\n`;
214
+
215
+ if (xSecret && xBearer) {
216
+ display += success('Ready for CRC challenge and events\n');
217
+ } else {
218
+ display += warning('Set X_WEBHOOK_SECRET and X_BEARER_TOKEN\n');
219
+ }
220
+ display += '\n';
221
+ }
222
+
223
+ // General webhook system
224
+ const kvConfigured = !!(process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN);
225
+ display += '**Storage System**\n';
226
+ display += `KV Store: ${kvConfigured ? '✅ Available' : '❌ Not configured'}\n`;
227
+ display += `Social Inbox: ${kvConfigured ? '✅ Active' : '❌ Disabled'}\n`;
228
+
229
+ if (!kvConfigured) {
230
+ display += warning('Set KV_REST_API_URL and KV_REST_API_TOKEN for event storage\n');
231
+ }
232
+ display += '\n';
233
+
234
+ display += divider();
235
+ display += '**Webhook URLs (for X Developer Portal):**\n';
236
+ display += '• Development: `https://yourapp.vercel.app/api/webhooks/x`\n';
237
+ display += '• Production: `https://vibe.fyi/api/webhooks/x`\n\n';
238
+ display += '**Required X Webhook Events:**\n';
239
+ display += '• Tweet create events (mentions)\n';
240
+ display += '• Direct message events\n';
241
+ display += '• Favorite events (optional)\n';
242
+ display += '• Follow events (optional)';
243
+
244
+ return { display };
245
+ }
246
+
247
+ async function getWebhookStats(endpoint) {
248
+ let display = header('Webhook Statistics');
249
+ display += '\n\n';
250
+
251
+ try {
252
+ const KV_CONFIGURED = !!(process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN);
253
+ if (!KV_CONFIGURED) {
254
+ display += warning('KV storage not configured - no stats available\n');
255
+ return { display };
256
+ }
257
+
258
+ const { kv } = await import('@vercel/kv');
259
+
260
+ // X webhook stats
261
+ if (endpoint === 'x' || endpoint === 'all') {
262
+ const xStats = await kv.hgetall('vibe:x_webhook_stats') || {};
263
+
264
+ display += '**X Webhook Stats**\n';
265
+ display += `Total deliveries: ${xStats.total_deliveries || 0}\n`;
266
+ display += `Events processed: ${xStats.events_processed || 0}\n`;
267
+ display += `Last delivery: ${xStats.last_delivery ? new Date(xStats.last_delivery).toLocaleString() : 'Never'}\n`;
268
+
269
+ // Daily stats (last 7 days)
270
+ const today = new Date();
271
+ const dailyStats = [];
272
+ for (let i = 6; i >= 0; i--) {
273
+ const date = new Date(today);
274
+ date.setDate(date.getDate() - i);
275
+ const dateKey = date.toISOString().split('T')[0];
276
+ const count = parseInt(xStats[`deliveries_${dateKey}`]) || 0;
277
+ if (count > 0) {
278
+ dailyStats.push(`${dateKey}: ${count}`);
279
+ }
280
+ }
281
+
282
+ if (dailyStats.length > 0) {
283
+ display += `Daily activity (last 7d): ${dailyStats.join(', ')}\n`;
284
+ }
285
+
286
+ display += '\n';
287
+ }
288
+
289
+ display += divider();
290
+ display += '**Health Check:**\n';
291
+ display += '• Test webhook: `vibe webhook-test --action test --endpoint x`\n';
292
+ display += '• View recent events: `vibe webhook-test --action events --limit 5`\n';
293
+ display += '• Check social inbox: `vibe social-inbox --channel webhooks`';
294
+
295
+ } catch (e) {
296
+ display += error(`Stats unavailable: ${e.message}\n`);
297
+ }
298
+
299
+ return { display };
300
+ }
301
+
302
+ async function getRecentEvents(endpoint, limit) {
303
+ let display = header(`Recent Webhook Events (${limit})`);
304
+ display += '\n\n';
305
+
306
+ try {
307
+ const KV_CONFIGURED = !!(process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN);
308
+ if (!KV_CONFIGURED) {
309
+ display += warning('KV storage not configured - no events available\n');
310
+ return { display };
311
+ }
312
+
313
+ const { kv } = await import('@vercel/kv');
314
+ const inboxKey = 'vibe:social_inbox';
315
+
316
+ const rawEvents = await kv.lrange(inboxKey, 0, limit - 1);
317
+
318
+ if (rawEvents.length === 0) {
319
+ display += '_No recent webhook events._\n\n';
320
+ display += 'Events will appear here when webhooks are triggered.\n';
321
+ display += 'Test with: `vibe webhook-test --action test --endpoint x`';
322
+ return { display };
323
+ }
324
+
325
+ display += `Found ${rawEvents.length} recent events:\n`;
326
+ display += divider();
327
+ display += '\n';
328
+
329
+ for (const eventStr of rawEvents) {
330
+ try {
331
+ const event = JSON.parse(eventStr);
332
+ const timeAgo = formatTimeAgo(new Date(event.timestamp));
333
+
334
+ display += `**${event.platform.toUpperCase()}** ${getTypeIcon(event.type)} — _${timeAgo}_\n`;
335
+ display += `From: @${event.from.handle} (${event.from.name})\n`;
336
+ display += `${event.content.slice(0, 100)}${event.content.length > 100 ? '...' : ''}\n`;
337
+
338
+ if (event.metadata?.url) {
339
+ display += `Link: ${event.metadata.url}\n`;
340
+ }
341
+
342
+ display += `Status: ${event.processed ? '✅ Processed' : '⏳ Pending'}\n`;
343
+ display += `_[${event.id}]_\n\n`;
344
+
345
+ } catch (e) {
346
+ display += `_Invalid event data_\n\n`;
347
+ }
348
+ }
349
+
350
+ display += divider();
351
+ display += '**Actions:**\n';
352
+ display += '• View in social inbox: `vibe social-inbox --include_webhooks`\n';
353
+ display += '• Reply to event: `vibe social-post --content "reply"`\n';
354
+ display += '• Clear events: `vibe webhook-test --action clear` (not implemented)';
355
+
356
+ } catch (e) {
357
+ display += error(`Failed to fetch events: ${e.message}\n`);
358
+ }
359
+
360
+ return { display };
361
+ }
362
+
363
+ function formatTimeAgo(timestamp) {
364
+ const now = Date.now();
365
+ const diff = now - timestamp;
366
+ const minutes = Math.floor(diff / (1000 * 60));
367
+ const hours = Math.floor(minutes / 60);
368
+ const days = Math.floor(hours / 24);
369
+
370
+ if (days > 0) return `${days}d ago`;
371
+ if (hours > 0) return `${hours}h ago`;
372
+ if (minutes > 0) return `${minutes}m ago`;
373
+ return 'just now';
374
+ }
375
+
376
+ function getTypeIcon(type) {
377
+ const icons = {
378
+ mention: '@',
379
+ reply: '↩️',
380
+ dm: '✉️',
381
+ like: '❤️',
382
+ repost: '🔁',
383
+ follow: '👤'
384
+ };
385
+ return icons[type] || '📡';
386
+ }
387
+
388
+ module.exports = { definition, handler };
package/tools/who.js CHANGED
@@ -24,7 +24,7 @@ const definition = {
24
24
  }
25
25
  };
26
26
 
27
- // Activity heat based on session signals
27
+ // Activity heat based on session signals + GitHub activity
28
28
  function getHeat(user) {
29
29
  const lastSeenMs = user.lastSeen;
30
30
  const now = Date.now();
@@ -38,6 +38,16 @@ function getHeat(user) {
38
38
  }
39
39
  }
40
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
+
41
51
  // Check for inferred state from smart detection
42
52
  if (user.mood_inferred && user.mood) {
43
53
  const inferredLabel = user.inferred_state
@@ -73,6 +83,11 @@ function getHeat(user) {
73
83
  return { icon: '🔥', label: 'shipping', inferred: false };
74
84
  }
75
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' };
89
+ }
90
+
76
91
  // Default based on recency
77
92
  if (minutesAgo < 2) {
78
93
  return { icon: '⚡', label: 'active', inferred: false };
@@ -116,6 +131,13 @@ function formatActivity(user) {
116
131
  return parts.join(' ');
117
132
  }
118
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
+
119
141
  // Fall back to one_liner
120
142
  return user.one_liner || 'Building something';
121
143
  }
@@ -128,6 +150,19 @@ async function handler(args) {
128
150
  // Apply smart detection — infer states from context signals
129
151
  const users = enhanceUsersWithInference(rawUsers);
130
152
  const myHandle = config.getHandle();
153
+ const apiUrl = config.getApiUrl();
154
+
155
+ // Fetch active help requests (non-blocking)
156
+ let helpRequests = [];
157
+ try {
158
+ const helpResponse = await fetch(`${apiUrl}/api/help?limit=5`);
159
+ const helpData = await helpResponse.json();
160
+ if (helpData.success && helpData.requests) {
161
+ helpRequests = helpData.requests;
162
+ }
163
+ } catch (e) {
164
+ // Silent fail - help requests are nice-to-have
165
+ }
131
166
 
132
167
  // Check for notifications (presence + messages)
133
168
  notify.checkAll(store);
@@ -139,7 +174,7 @@ async function handler(args) {
139
174
  _You're the only one here right now..._
140
175
 
141
176
  🎮 **Challenge someone later**: "play tictactoe with @friend"
142
- 💬 **Ping @seth**: He's probably around somewhere
177
+ 💬 **Message @vibe**: Say hello to the platform!
143
178
  🔗 **Invite a friend**: Share \`slashvibe.dev\`
144
179
 
145
180
  _Check back in a bit — builders come and go._`
@@ -155,6 +190,17 @@ _Check back in a bit — builders come and go._`
155
190
 
156
191
  let display = `## Who's Around\n\n`;
157
192
 
193
+ // Help requests section (if any)
194
+ if (helpRequests.length > 0) {
195
+ display += `**🆘 Needs Help:**\n`;
196
+ for (const req of helpRequests.slice(0, 3)) {
197
+ const urgencyIcon = req.urgency === 'high' ? '🔴' : req.urgency === 'low' ? '🟢' : '🟡';
198
+ const shortProblem = req.problem.length > 50 ? req.problem.slice(0, 47) + '...' : req.problem;
199
+ display += `${urgencyIcon} **@${req.handle}**: ${shortProblem}\n`;
200
+ }
201
+ display += `→ \`vibe stuck\` to help or ask\n\n---\n\n`;
202
+ }
203
+
158
204
  // Activity section for active users
159
205
  if (active.length > 0) {
160
206
  active.forEach(u => {
@@ -168,7 +214,12 @@ _Check back in a bit — builders come and go._`
168
214
  const activity = formatActivity(u);
169
215
  const timeAgo = formatTimeAgo(u.lastSeen);
170
216
 
171
- display += `${heat.icon} **@${u.handle}**${agentBadge}${tag}${heatLabel}\n`;
217
+ // Show availability topics if set
218
+ const availableFor = u.availableFor && Array.isArray(u.availableFor) && u.availableFor.length > 0
219
+ ? ` — 🤝 _available for: ${u.availableFor.join(', ')}_`
220
+ : '';
221
+
222
+ display += `${heat.icon} **@${u.handle}**${agentBadge}${tag}${heatLabel}${availableFor}\n`;
172
223
  if (operatorTag) {
173
224
  display += ` ${operatorTag}\n`;
174
225
  }
@@ -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 };