slashvibe-mcp 0.3.21 → 0.3.23

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 (235) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +280 -47
  3. package/auto-update.js +10 -15
  4. package/config.js +36 -31
  5. package/crypto.js +1 -6
  6. package/debug.js +12 -0
  7. package/discord.js +19 -19
  8. package/eslint.config.js +54 -0
  9. package/index.js +217 -207
  10. package/intelligence/index.js +2 -9
  11. package/intelligence/infer.js +10 -16
  12. package/intelligence/patterns.js +23 -18
  13. package/intelligence/proactive.js +16 -15
  14. package/intelligence/serendipity.js +57 -20
  15. package/memory.js +13 -8
  16. package/migrate-v2.js +72 -0
  17. package/notification-emitter.js +2 -2
  18. package/notify.js +39 -14
  19. package/package.json +28 -29
  20. package/post-install.js +141 -0
  21. package/presence.js +2 -2
  22. package/prompts.js +5 -9
  23. package/protocol/index.js +123 -87
  24. package/protocol/telegram-commands.js +36 -37
  25. package/store/api.js +358 -529
  26. package/store/local.js +9 -10
  27. package/store/profiles.js +48 -192
  28. package/store/reservations.js +2 -9
  29. package/store/skills.js +69 -71
  30. package/store/sqlite.js +355 -0
  31. package/test-skills-bootstrap.js +20 -0
  32. package/test-v2-integration.js +385 -0
  33. package/tools/_actions.js +48 -387
  34. package/tools/_connection-queue.js +45 -56
  35. package/tools/_discovery-enhanced.js +52 -57
  36. package/tools/_discovery.js +87 -185
  37. package/tools/{l2-status.js → _experimental/l2-status.js} +68 -70
  38. package/tools/{shipback.js → _experimental/shipback.js} +4 -3
  39. package/tools/_proactive-discovery.js +60 -73
  40. package/tools/_shared/index.js +41 -64
  41. package/tools/admin-inbox.js +10 -15
  42. package/tools/agents.js +1 -1
  43. package/tools/artifact-create.js +13 -23
  44. package/tools/artifact-view.js +4 -4
  45. package/tools/{_deprecated/back.js → back.js} +1 -1
  46. package/tools/bye.js +3 -5
  47. package/tools/consent.js +2 -2
  48. package/tools/context.js +9 -10
  49. package/tools/crossword.js +3 -2
  50. package/tools/discover.js +94 -356
  51. package/tools/dm.js +27 -86
  52. package/tools/doctor.js +12 -41
  53. package/tools/drawing.js +34 -20
  54. package/tools/echo.js +11 -11
  55. package/tools/feed.js +30 -58
  56. package/tools/follow.js +64 -187
  57. package/tools/{_deprecated/forget.js → forget.js} +4 -7
  58. package/tools/game.js +144 -48
  59. package/tools/handoff.js +6 -8
  60. package/tools/help.js +3 -3
  61. package/tools/idea.js +15 -27
  62. package/tools/inbox.js +121 -293
  63. package/tools/init.js +54 -151
  64. package/tools/invite.js +8 -21
  65. package/tools/migrate.js +27 -24
  66. package/tools/multiplayer-game.js +50 -40
  67. package/tools/{_deprecated/mute.js → mute.js} +4 -3
  68. package/tools/notifications.js +58 -48
  69. package/tools/observe.js +12 -15
  70. package/tools/onboarding.js +8 -11
  71. package/tools/open.js +13 -144
  72. package/tools/party-game.js +23 -12
  73. package/tools/patterns.js +2 -1
  74. package/tools/ping.js +5 -7
  75. package/tools/react.js +28 -30
  76. package/tools/{_deprecated/recall.js → recall.js} +5 -10
  77. package/tools/release.js +4 -2
  78. package/tools/{_deprecated/remember.js → remember.js} +4 -6
  79. package/tools/report.js +2 -2
  80. package/tools/request.js +6 -26
  81. package/tools/reserve.js +1 -1
  82. package/tools/session-fork.js +97 -0
  83. package/tools/session-save.js +109 -0
  84. package/tools/settings.js +30 -99
  85. package/tools/ship.js +74 -56
  86. package/tools/{_deprecated/skills-exchange.js → skills-exchange.js} +38 -39
  87. package/tools/social-inbox.js +22 -28
  88. package/tools/social-post.js +24 -27
  89. package/tools/solo-game.js +54 -46
  90. package/tools/start.js +14 -148
  91. package/tools/status.js +21 -68
  92. package/tools/submit.js +4 -2
  93. package/tools/suggest-tags.js +36 -33
  94. package/tools/summarize.js +19 -16
  95. package/tools/tag-suggestions.js +72 -73
  96. package/tools/test.js +1 -1
  97. package/tools/{_deprecated/tictactoe.js → tictactoe.js} +26 -26
  98. package/tools/token.js +4 -4
  99. package/tools/update.js +1 -2
  100. package/tools/watch.js +132 -112
  101. package/tools/who.js +20 -40
  102. package/tools/{_deprecated/wordassociation.js → wordassociation.js} +23 -20
  103. package/tools/workshop-buddy.js +52 -53
  104. package/tools/x-mentions.js +0 -1
  105. package/tools/x-reply.js +0 -1
  106. package/twitter.js +14 -20
  107. package/version.json +8 -10
  108. package/webhook-runner.js +132 -0
  109. package/auth-store.js +0 -148
  110. package/bridges/bridge-monitor.js +0 -388
  111. package/bridges/discord-bot.js +0 -431
  112. package/bridges/farcaster.js +0 -299
  113. package/bridges/telegram.js +0 -261
  114. package/bridges/webhook-health.js +0 -420
  115. package/bridges/webhook-server.js +0 -437
  116. package/bridges/whatsapp.js +0 -441
  117. package/bridges/x-webhook.js +0 -423
  118. package/games/arcade.js +0 -406
  119. package/games/chess.js +0 -451
  120. package/games/colorguess.js +0 -343
  121. package/games/crossword-words.js +0 -171
  122. package/games/crossword.js +0 -461
  123. package/games/drawing.js +0 -347
  124. package/games/gameroulette.js +0 -300
  125. package/games/gamerouter.js +0 -336
  126. package/games/gamestatus.js +0 -337
  127. package/games/guessnumber.js +0 -209
  128. package/games/hangman.js +0 -279
  129. package/games/memory.js +0 -338
  130. package/games/multiplayer-tictactoe.js +0 -389
  131. package/games/pixelart.js +0 -399
  132. package/games/quickduel.js +0 -354
  133. package/games/riddle.js +0 -371
  134. package/games/rockpaperscissors.js +0 -291
  135. package/games/snake.js +0 -406
  136. package/games/storybuilder.js +0 -343
  137. package/games/tictactoe.js +0 -345
  138. package/games/twentyquestions.js +0 -286
  139. package/games/twotruths.js +0 -207
  140. package/games/werewolf.js +0 -508
  141. package/games/wordassociation.js +0 -247
  142. package/games/wordchain.js +0 -135
  143. package/intelligence/interests.js +0 -369
  144. package/setup.js +0 -480
  145. package/smart-inbox.js +0 -276
  146. package/tools/_deprecated/auto-suggest-connections.js +0 -304
  147. package/tools/_deprecated/bootstrap-skills.js +0 -231
  148. package/tools/_deprecated/bridge-dashboard.js +0 -342
  149. package/tools/_deprecated/bridge-health.js +0 -400
  150. package/tools/_deprecated/bridge-live.js +0 -384
  151. package/tools/_deprecated/bridges.js +0 -383
  152. package/tools/_deprecated/colorguess.js +0 -281
  153. package/tools/_deprecated/discover-insights.js +0 -379
  154. package/tools/_deprecated/discover-momentum.js +0 -256
  155. package/tools/_deprecated/discovery-analytics.js +0 -345
  156. package/tools/_deprecated/discovery-auto-suggest.js +0 -275
  157. package/tools/_deprecated/discovery-bootstrap.js +0 -267
  158. package/tools/_deprecated/discovery-daily.js +0 -375
  159. package/tools/_deprecated/discovery-dashboard.js +0 -385
  160. package/tools/_deprecated/discovery-digest.js +0 -314
  161. package/tools/_deprecated/discovery-hub.js +0 -357
  162. package/tools/_deprecated/discovery-insights.js +0 -384
  163. package/tools/_deprecated/discovery-momentum.js +0 -281
  164. package/tools/_deprecated/discovery-monitor.js +0 -319
  165. package/tools/_deprecated/discovery-proactive.js +0 -300
  166. package/tools/_deprecated/draw.js +0 -317
  167. package/tools/_deprecated/farcaster.js +0 -307
  168. package/tools/_deprecated/games-catalog.js +0 -376
  169. package/tools/_deprecated/games.js +0 -313
  170. package/tools/_deprecated/guessnumber.js +0 -194
  171. package/tools/_deprecated/hangman.js +0 -129
  172. package/tools/_deprecated/multiplayer-tictactoe.js +0 -303
  173. package/tools/_deprecated/riddle.js +0 -240
  174. package/tools/_deprecated/run-bootstrap.js +0 -69
  175. package/tools/_deprecated/skills-analytics.js +0 -349
  176. package/tools/_deprecated/skills-bootstrap.js +0 -301
  177. package/tools/_deprecated/skills-dashboard.js +0 -268
  178. package/tools/_deprecated/skills.js +0 -380
  179. package/tools/_deprecated/smart-intro.js +0 -353
  180. package/tools/_deprecated/storybuilder.js +0 -331
  181. package/tools/_deprecated/telegram-bot.js +0 -183
  182. package/tools/_deprecated/telegram-setup.js +0 -214
  183. package/tools/_deprecated/twentyquestions.js +0 -143
  184. package/tools/_shared.js +0 -234
  185. package/tools/_work-context.js +0 -338
  186. package/tools/_work-context.manual-test.js +0 -199
  187. package/tools/_work-context.test.js +0 -260
  188. package/tools/activity.js +0 -220
  189. package/tools/agent-treasury.js +0 -288
  190. package/tools/analytics.js +0 -191
  191. package/tools/approve.js +0 -197
  192. package/tools/arcade.js +0 -173
  193. package/tools/artifacts-price.js +0 -107
  194. package/tools/ask-expert.js +0 -160
  195. package/tools/available.js +0 -120
  196. package/tools/become-expert.js +0 -150
  197. package/tools/broadcast.js +0 -325
  198. package/tools/chat.js +0 -202
  199. package/tools/collaborative-drawing.js +0 -286
  200. package/tools/connection-status.js +0 -178
  201. package/tools/earnings.js +0 -126
  202. package/tools/friends.js +0 -207
  203. package/tools/genesis.js +0 -233
  204. package/tools/gig-browse.js +0 -206
  205. package/tools/gig-complete.js +0 -144
  206. package/tools/health.js +0 -87
  207. package/tools/leaderboard.js +0 -117
  208. package/tools/lib/git-apply.js +0 -206
  209. package/tools/lib/git-bundle.js +0 -407
  210. package/tools/mint.js +0 -377
  211. package/tools/plan.js +0 -225
  212. package/tools/profile.js +0 -219
  213. package/tools/proof-of-work.js +0 -144
  214. package/tools/pulse.js +0 -218
  215. package/tools/reply.js +0 -166
  216. package/tools/reputation.js +0 -175
  217. package/tools/schedule.js +0 -367
  218. package/tools/search-messages.js +0 -123
  219. package/tools/session.js +0 -467
  220. package/tools/session_price.js +0 -128
  221. package/tools/smart-check.js +0 -201
  222. package/tools/social-processor.js +0 -445
  223. package/tools/streak.js +0 -147
  224. package/tools/stuck.js +0 -297
  225. package/tools/subscribe.js +0 -148
  226. package/tools/subscriptions.js +0 -134
  227. package/tools/tip.js +0 -193
  228. package/tools/wallet.js +0 -269
  229. package/tools/webhook-test.js +0 -388
  230. package/tools/withdraw.js +0 -145
  231. package/tools/work-summary.js +0 -96
  232. package/tools/workshop.js +0 -327
  233. /package/tools/{l2-bridge.js → _experimental/l2-bridge.js} +0 -0
  234. /package/tools/{l2.js → _experimental/l2.js} +0 -0
  235. /package/tools/{_deprecated/away.js → away.js} +0 -0
@@ -1,384 +0,0 @@
1
- /**
2
- * vibe bridge-live — Real-time bridge activity monitor
3
- *
4
- * Live dashboard showing incoming messages, outgoing posts, and bridge health.
5
- * Perfect for monitoring social activity during /vibe sessions.
6
- */
7
-
8
- const twitter = require('../twitter');
9
- const telegram = require('../bridges/telegram');
10
- const farcaster = require('../bridges/farcaster');
11
- const { requireInit, header, divider, success, warning } = require('./_shared');
12
-
13
- const definition = {
14
- name: 'vibe_bridge_live',
15
- description: 'Real-time monitor of social bridge activity and health',
16
- inputSchema: {
17
- type: 'object',
18
- properties: {
19
- duration: {
20
- type: 'number',
21
- description: 'Monitor duration in seconds (default: 30, max: 300)'
22
- },
23
- refresh_interval: {
24
- type: 'number',
25
- description: 'Refresh interval in seconds (default: 5, min: 2)'
26
- },
27
- platforms: {
28
- type: 'array',
29
- items: {
30
- type: 'string',
31
- enum: ['x', 'telegram', 'farcaster', 'discord']
32
- },
33
- description: 'Platforms to monitor (default: all configured)'
34
- },
35
- show_content: {
36
- type: 'boolean',
37
- description: 'Show message content preview (default: true)'
38
- }
39
- }
40
- }
41
- };
42
-
43
- async function handler(args) {
44
- const initCheck = requireInit();
45
- if (initCheck) return initCheck;
46
-
47
- const {
48
- duration = 30,
49
- refresh_interval = 5,
50
- platforms,
51
- show_content = true
52
- } = args;
53
-
54
- // Validate inputs
55
- if (duration > 300) {
56
- return { display: 'Maximum duration is 300 seconds (5 minutes)' };
57
- }
58
-
59
- if (refresh_interval < 2) {
60
- return { display: 'Minimum refresh interval is 2 seconds' };
61
- }
62
-
63
- const startTime = Date.now();
64
- const endTime = startTime + (duration * 1000);
65
- const interval = refresh_interval * 1000;
66
-
67
- let display = header('Bridge Live Monitor');
68
- display += `\\n\\nStarted: ${new Date().toLocaleTimeString()}\\n`;
69
- display += `Duration: ${duration}s | Refresh: ${refresh_interval}s\\n`;
70
- display += divider();
71
- display += '\\n';
72
-
73
- // Initial snapshot
74
- const activity = {
75
- sessions: 0,
76
- messages: [],
77
- errors: [],
78
- platforms: await getActivePlatforms(platforms)
79
- };
80
-
81
- // Show initial state
82
- display += await formatLiveSnapshot(activity, show_content);
83
-
84
- // Simulated live monitoring (in real implementation, this would be event-driven)
85
- let currentTime = Date.now();
86
- let sessionCount = 1;
87
-
88
- while (currentTime < endTime) {
89
- // Wait for next refresh
90
- await sleep(interval);
91
- currentTime = Date.now();
92
- sessionCount++;
93
-
94
- // Simulate new activity (in real implementation, this would pull from event queues)
95
- const newActivity = await collectRecentActivity(activity.platforms, interval / 1000);
96
-
97
- if (newActivity.length > 0) {
98
- activity.messages.push(...newActivity);
99
- activity.sessions = sessionCount;
100
-
101
- // Keep only recent messages (last 10)
102
- activity.messages = activity.messages.slice(-10);
103
-
104
- // Update display with new activity
105
- display += `\\n\\n**Update ${sessionCount}** (${new Date().toLocaleTimeString()})\\n`;
106
- display += formatNewActivity(newActivity, show_content);
107
- }
108
-
109
- // Check for platform issues
110
- const healthIssues = await checkForNewIssues(activity.platforms);
111
- if (healthIssues.length > 0) {
112
- activity.errors.push(...healthIssues);
113
- display += '\\n' + warning('New issues detected:\\n' + healthIssues.map(e => `• ${e}`).join('\\n'));
114
- }
115
-
116
- // Progress indicator
117
- const elapsed = (currentTime - startTime) / 1000;
118
- const remaining = Math.max(0, duration - elapsed);
119
- display += `\\n\\n_Monitoring... ${remaining.toFixed(0)}s remaining_`;
120
-
121
- if (remaining <= 0) break;
122
- }
123
-
124
- // Final summary
125
- display += '\\n\\n' + divider();
126
- display += formatFinalSummary(activity, duration);
127
-
128
- return { display };
129
- }
130
-
131
- async function getActivePlatforms(requestedPlatforms) {
132
- const platforms = {};
133
-
134
- // Check which platforms are configured and active
135
- const available = ['x', 'telegram', 'farcaster', 'discord'];
136
- const toCheck = requestedPlatforms || available;
137
-
138
- for (const platform of toCheck) {
139
- switch (platform) {
140
- case 'x':
141
- if (twitter.isConfigured()) {
142
- try {
143
- const me = await twitter.getMe();
144
- platforms.x = {
145
- active: true,
146
- username: me.data.username,
147
- lastActivity: Date.now()
148
- };
149
- } catch (e) {
150
- platforms.x = { active: false, error: e.message };
151
- }
152
- }
153
- break;
154
-
155
- case 'telegram':
156
- if (telegram.isConfigured()) {
157
- try {
158
- const bot = await telegram.getBotInfo();
159
- platforms.telegram = {
160
- active: true,
161
- username: bot.username,
162
- lastActivity: Date.now()
163
- };
164
- } catch (e) {
165
- platforms.telegram = { active: false, error: e.message };
166
- }
167
- }
168
- break;
169
-
170
- case 'farcaster':
171
- if (farcaster.isConfigured()) {
172
- try {
173
- const user = await farcaster.getUser();
174
- platforms.farcaster = {
175
- active: true,
176
- username: user.users[0].username,
177
- lastActivity: Date.now()
178
- };
179
- } catch (e) {
180
- platforms.farcaster = { active: false, error: e.message };
181
- }
182
- }
183
- break;
184
-
185
- case 'discord':
186
- // Discord webhook doesn't provide status info
187
- platforms.discord = {
188
- active: false, // Assume webhook-only for now
189
- username: 'webhook',
190
- lastActivity: Date.now()
191
- };
192
- break;
193
- }
194
- }
195
-
196
- return platforms;
197
- }
198
-
199
- async function formatLiveSnapshot(activity, showContent) {
200
- let result = '**Active Platforms:**\\n';
201
-
202
- const activePlatforms = Object.entries(activity.platforms)
203
- .filter(([_, platform]) => platform.active);
204
-
205
- if (activePlatforms.length === 0) {
206
- result += '_No active platforms_\\n\\n';
207
- result += 'Configure platforms with: `vibe bridges`\\n';
208
- return result;
209
- }
210
-
211
- for (const [name, platform] of activePlatforms) {
212
- const icon = platform.active ? '🟢' : '🔴';
213
- result += `${icon} **${name.toUpperCase()}** (@${platform.username})\\n`;
214
- }
215
-
216
- result += '\\n**Recent Activity:**\\n';
217
- if (activity.messages.length === 0) {
218
- result += '_No recent messages_\\n';
219
- } else {
220
- result += formatMessages(activity.messages.slice(-5), showContent);
221
- }
222
-
223
- return result;
224
- }
225
-
226
- async function collectRecentActivity(platforms, intervalSeconds) {
227
- const activity = [];
228
-
229
- // In a real implementation, this would:
230
- // 1. Check webhook queues for new messages
231
- // 2. Poll APIs for mentions/DMs since last check
232
- // 3. Monitor outgoing message queues
233
- // 4. Track rate limit status
234
-
235
- // For demo, simulate some activity
236
- const activePlatforms = Object.keys(platforms).filter(p => platforms[p].active);
237
-
238
- if (activePlatforms.length > 0 && Math.random() < 0.3) {
239
- const randomPlatform = activePlatforms[Math.floor(Math.random() * activePlatforms.length)];
240
-
241
- activity.push({
242
- platform: randomPlatform,
243
- type: Math.random() < 0.7 ? 'mention' : 'dm',
244
- from: `user_${Math.floor(Math.random() * 1000)}`,
245
- content: generateSampleMessage(),
246
- timestamp: new Date().toISOString(),
247
- processed: true
248
- });
249
- }
250
-
251
- return activity;
252
- }
253
-
254
- function generateSampleMessage() {
255
- const samples = [
256
- 'Hey! Checking out /vibe',
257
- 'Love the unified social interface',
258
- 'How do I post to multiple channels?',
259
- 'The bridge system is really smooth',
260
- 'Just shipped a new feature!',
261
- '/status shipping working on something cool'
262
- ];
263
-
264
- return samples[Math.floor(Math.random() * samples.length)];
265
- }
266
-
267
- async function checkForNewIssues(platforms) {
268
- const issues = [];
269
-
270
- // Simulate health checks
271
- for (const [name, platform] of Object.entries(platforms)) {
272
- if (platform.active && Math.random() < 0.05) { // 5% chance of issue
273
- issues.push(`${name}: Rate limit approaching`);
274
- }
275
- }
276
-
277
- return issues;
278
- }
279
-
280
- function formatNewActivity(activities, showContent) {
281
- if (activities.length === 0) return '_No new activity_\\n';
282
-
283
- let result = '';
284
- for (const activity of activities) {
285
- const icon = getActivityIcon(activity.type);
286
- const platform = activity.platform.toUpperCase();
287
-
288
- result += `${icon} **${platform}** from @${activity.from}`;
289
-
290
- if (showContent && activity.content) {
291
- const preview = activity.content.length > 50
292
- ? activity.content.slice(0, 50) + '...'
293
- : activity.content;
294
- result += ` — "${preview}"`;
295
- }
296
-
297
- result += `\\n`;
298
- }
299
-
300
- return result;
301
- }
302
-
303
- function formatMessages(messages, showContent) {
304
- let result = '';
305
-
306
- for (const msg of messages) {
307
- const timeAgo = formatTimeAgo(new Date(msg.timestamp));
308
- const icon = getActivityIcon(msg.type);
309
- const platform = msg.platform.toUpperCase();
310
-
311
- result += `${icon} **${platform}** @${msg.from} — _${timeAgo}_\\n`;
312
-
313
- if (showContent && msg.content) {
314
- const preview = msg.content.length > 80
315
- ? msg.content.slice(0, 80) + '...'
316
- : msg.content;
317
- result += ` "${preview}"\\n`;
318
- }
319
-
320
- result += '\\n';
321
- }
322
-
323
- return result;
324
- }
325
-
326
- function formatFinalSummary(activity, duration) {
327
- const totalMessages = activity.messages.length;
328
- const activePlatforms = Object.values(activity.platforms).filter(p => p.active).length;
329
- const errorCount = activity.errors.length;
330
-
331
- let result = success(`**Live Monitor Complete**\\n\\n`);
332
- result += `**Summary:**\\n`;
333
- result += `• Duration: ${duration} seconds\\n`;
334
- result += `• Active platforms: ${activePlatforms}\\n`;
335
- result += `• Messages processed: ${totalMessages}\\n`;
336
- result += `• Errors: ${errorCount}\\n\\n`;
337
-
338
- if (activity.messages.length > 0) {
339
- result += `**Most Recent Messages:**\\n`;
340
- result += formatMessages(activity.messages.slice(-3), true);
341
- }
342
-
343
- if (activity.errors.length > 0) {
344
- result += `\\n**Issues Detected:**\\n`;
345
- result += activity.errors.map(e => `• ${e}`).join('\\n');
346
- result += '\\n\\nRun `vibe bridge-health` for detailed diagnostics.';
347
- }
348
-
349
- result += '\\n\\n**Next Steps:**\\n';
350
- result += '• Check unified inbox: `vibe social-inbox --refresh`\\n';
351
- result += '• Post across platforms: `vibe social-post "message" --channels ["x","telegram"]`\\n';
352
- result += '• Monitor health: `vibe bridge-health --details`';
353
-
354
- return result;
355
- }
356
-
357
- function getActivityIcon(type) {
358
- const icons = {
359
- mention: '@',
360
- dm: '✉️',
361
- reply: '↩️',
362
- like: '❤️',
363
- repost: '🔁',
364
- post: '📤'
365
- };
366
- return icons[type] || '📨';
367
- }
368
-
369
- function formatTimeAgo(date) {
370
- const now = new Date();
371
- const diffMs = now - date;
372
- const diffSecs = Math.floor(diffMs / 1000);
373
- const diffMins = Math.floor(diffSecs / 60);
374
-
375
- if (diffSecs < 60) return `${diffSecs}s ago`;
376
- if (diffMins < 60) return `${diffMins}m ago`;
377
- return date.toLocaleTimeString();
378
- }
379
-
380
- function sleep(ms) {
381
- return new Promise(resolve => setTimeout(resolve, ms));
382
- }
383
-
384
- module.exports = { definition, handler };