slashvibe-mcp 0.3.20 → 0.3.21

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 (167) hide show
  1. package/README.md +47 -252
  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 +27 -15
  14. package/games/arcade.js +406 -0
  15. package/games/chess.js +451 -0
  16. package/games/colorguess.js +343 -0
  17. package/games/crossword-words.js +171 -0
  18. package/games/crossword.js +461 -0
  19. package/games/drawing.js +347 -0
  20. package/games/gameroulette.js +300 -0
  21. package/games/gamerouter.js +336 -0
  22. package/games/gamestatus.js +337 -0
  23. package/games/guessnumber.js +209 -0
  24. package/games/hangman.js +279 -0
  25. package/games/memory.js +338 -0
  26. package/games/multiplayer-tictactoe.js +389 -0
  27. package/games/pixelart.js +399 -0
  28. package/games/quickduel.js +354 -0
  29. package/games/riddle.js +371 -0
  30. package/games/rockpaperscissors.js +291 -0
  31. package/games/snake.js +406 -0
  32. package/games/storybuilder.js +343 -0
  33. package/games/tictactoe.js +345 -0
  34. package/games/twentyquestions.js +286 -0
  35. package/games/twotruths.js +207 -0
  36. package/games/werewolf.js +508 -0
  37. package/games/wordassociation.js +247 -0
  38. package/games/wordchain.js +135 -0
  39. package/index.js +116 -159
  40. package/intelligence/index.js +9 -2
  41. package/intelligence/interests.js +369 -0
  42. package/notification-emitter.js +77 -0
  43. package/notify.js +5 -1
  44. package/package.json +21 -16
  45. package/prompts.js +1 -1
  46. package/protocol/index.js +73 -0
  47. package/setup.js +480 -0
  48. package/smart-inbox.js +276 -0
  49. package/store/api.js +536 -215
  50. package/store/profiles.js +160 -12
  51. package/tools/_actions.js +362 -21
  52. package/tools/_discovery.js +119 -26
  53. package/tools/_shared/index.js +64 -0
  54. package/tools/_shared.js +234 -0
  55. package/tools/_work-context.js +338 -0
  56. package/tools/_work-context.manual-test.js +199 -0
  57. package/tools/_work-context.test.js +260 -0
  58. package/tools/activity.js +220 -0
  59. package/tools/analytics.js +191 -0
  60. package/tools/approve.js +197 -0
  61. package/tools/artifact-create.js +14 -3
  62. package/tools/artifacts-price.js +107 -0
  63. package/tools/available.js +120 -0
  64. package/tools/broadcast.js +325 -0
  65. package/tools/chat.js +202 -0
  66. package/tools/collaborative-drawing.js +1 -1
  67. package/tools/connection-status.js +178 -0
  68. package/tools/discover.js +350 -34
  69. package/tools/dm.js +80 -8
  70. package/tools/earnings.js +126 -0
  71. package/tools/feed.js +35 -4
  72. package/tools/follow.js +224 -0
  73. package/tools/friends.js +207 -0
  74. package/tools/gig-browse.js +206 -0
  75. package/tools/gig-complete.js +144 -0
  76. package/tools/health.js +87 -0
  77. package/tools/help.js +3 -3
  78. package/tools/idea.js +9 -2
  79. package/tools/inbox.js +289 -105
  80. package/tools/init.js +131 -34
  81. package/tools/invite.js +15 -4
  82. package/tools/leaderboard.js +117 -0
  83. package/tools/lib/git-apply.js +206 -0
  84. package/tools/lib/git-bundle.js +407 -0
  85. package/tools/migrate.js +3 -3
  86. package/tools/multiplayer-game.js +1 -1
  87. package/tools/onboarding.js +7 -7
  88. package/tools/open.js +143 -12
  89. package/tools/party-game.js +1 -1
  90. package/tools/plan.js +225 -0
  91. package/tools/proof-of-work.js +144 -0
  92. package/tools/reply.js +166 -0
  93. package/tools/report.js +1 -1
  94. package/tools/request.js +17 -3
  95. package/tools/schedule.js +367 -0
  96. package/tools/search-messages.js +123 -0
  97. package/tools/session.js +467 -0
  98. package/tools/session_price.js +128 -0
  99. package/tools/settings.js +90 -2
  100. package/tools/ship.js +30 -7
  101. package/tools/smart-check.js +201 -0
  102. package/tools/start.js +147 -12
  103. package/tools/status.js +53 -6
  104. package/tools/streak.js +147 -0
  105. package/tools/stuck.js +297 -0
  106. package/tools/subscribe.js +148 -0
  107. package/tools/subscriptions.js +134 -0
  108. package/tools/suggest-tags.js +6 -8
  109. package/tools/tag-suggestions.js +1 -1
  110. package/tools/tip.js +150 -77
  111. package/tools/token.js +4 -4
  112. package/tools/update.js +1 -1
  113. package/tools/wallet.js +221 -79
  114. package/tools/watch.js +157 -0
  115. package/tools/who.js +30 -1
  116. package/tools/withdraw.js +145 -0
  117. package/tools/work-summary.js +96 -0
  118. package/version.json +10 -8
  119. package/LICENSE +0 -21
  120. package/store/sqlite.js +0 -347
  121. /package/tools/{auto-suggest-connections.js → _deprecated/auto-suggest-connections.js} +0 -0
  122. /package/tools/{away.js → _deprecated/away.js} +0 -0
  123. /package/tools/{back.js → _deprecated/back.js} +0 -0
  124. /package/tools/{bootstrap-skills.js → _deprecated/bootstrap-skills.js} +0 -0
  125. /package/tools/{bridge-dashboard.js → _deprecated/bridge-dashboard.js} +0 -0
  126. /package/tools/{bridge-health.js → _deprecated/bridge-health.js} +0 -0
  127. /package/tools/{bridge-live.js → _deprecated/bridge-live.js} +0 -0
  128. /package/tools/{bridges.js → _deprecated/bridges.js} +0 -0
  129. /package/tools/{colorguess.js → _deprecated/colorguess.js} +0 -0
  130. /package/tools/{discover-insights.js → _deprecated/discover-insights.js} +0 -0
  131. /package/tools/{discover-momentum.js → _deprecated/discover-momentum.js} +0 -0
  132. /package/tools/{discovery-analytics.js → _deprecated/discovery-analytics.js} +0 -0
  133. /package/tools/{discovery-auto-suggest.js → _deprecated/discovery-auto-suggest.js} +0 -0
  134. /package/tools/{discovery-bootstrap.js → _deprecated/discovery-bootstrap.js} +0 -0
  135. /package/tools/{discovery-daily.js → _deprecated/discovery-daily.js} +0 -0
  136. /package/tools/{discovery-dashboard.js → _deprecated/discovery-dashboard.js} +0 -0
  137. /package/tools/{discovery-digest.js → _deprecated/discovery-digest.js} +0 -0
  138. /package/tools/{discovery-hub.js → _deprecated/discovery-hub.js} +0 -0
  139. /package/tools/{discovery-insights.js → _deprecated/discovery-insights.js} +0 -0
  140. /package/tools/{discovery-momentum.js → _deprecated/discovery-momentum.js} +0 -0
  141. /package/tools/{discovery-monitor.js → _deprecated/discovery-monitor.js} +0 -0
  142. /package/tools/{discovery-proactive.js → _deprecated/discovery-proactive.js} +0 -0
  143. /package/tools/{draw.js → _deprecated/draw.js} +0 -0
  144. /package/tools/{farcaster.js → _deprecated/farcaster.js} +0 -0
  145. /package/tools/{forget.js → _deprecated/forget.js} +0 -0
  146. /package/tools/{games-catalog.js → _deprecated/games-catalog.js} +0 -0
  147. /package/tools/{games.js → _deprecated/games.js} +0 -0
  148. /package/tools/{guessnumber.js → _deprecated/guessnumber.js} +0 -0
  149. /package/tools/{hangman.js → _deprecated/hangman.js} +0 -0
  150. /package/tools/{multiplayer-tictactoe.js → _deprecated/multiplayer-tictactoe.js} +0 -0
  151. /package/tools/{mute.js → _deprecated/mute.js} +0 -0
  152. /package/tools/{recall.js → _deprecated/recall.js} +0 -0
  153. /package/tools/{remember.js → _deprecated/remember.js} +0 -0
  154. /package/tools/{riddle.js → _deprecated/riddle.js} +0 -0
  155. /package/tools/{run-bootstrap.js → _deprecated/run-bootstrap.js} +0 -0
  156. /package/tools/{skills-analytics.js → _deprecated/skills-analytics.js} +0 -0
  157. /package/tools/{skills-bootstrap.js → _deprecated/skills-bootstrap.js} +0 -0
  158. /package/tools/{skills-dashboard.js → _deprecated/skills-dashboard.js} +0 -0
  159. /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
  160. /package/tools/{skills.js → _deprecated/skills.js} +0 -0
  161. /package/tools/{smart-intro.js → _deprecated/smart-intro.js} +0 -0
  162. /package/tools/{storybuilder.js → _deprecated/storybuilder.js} +0 -0
  163. /package/tools/{telegram-bot.js → _deprecated/telegram-bot.js} +0 -0
  164. /package/tools/{telegram-setup.js → _deprecated/telegram-setup.js} +0 -0
  165. /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
  166. /package/tools/{twentyquestions.js → _deprecated/twentyquestions.js} +0 -0
  167. /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Interest Inference — Derive interests from live context signals
3
+ *
4
+ * Complements static inference (from building descriptions) with DYNAMIC
5
+ * interests inferred from what users are ACTUALLY doing:
6
+ * - Current file/module they're editing
7
+ * - Branch name they're working on
8
+ * - Activity patterns (shipping, debugging, etc.)
9
+ *
10
+ * This creates a more accurate picture of interests that updates in real-time.
11
+ */
12
+
13
+ const { getModule, extractBranchTopic } = require('./serendipity');
14
+
15
+ // Map file modules to interest categories
16
+ const MODULE_TO_INTERESTS = {
17
+ // Code organization
18
+ 'agents': ['agents', 'ai', 'automation'],
19
+ 'agent': ['agents', 'ai', 'automation'],
20
+ 'mcp': ['agents', 'mcp', 'ai'],
21
+ 'ai': ['ai', 'machine learning'],
22
+ 'ml': ['machine learning', 'ai', 'data science'],
23
+ 'llm': ['ai', 'machine learning'],
24
+
25
+ // Web development
26
+ 'api': ['backend', 'api'],
27
+ 'components': ['frontend', 'web development'],
28
+ 'pages': ['frontend', 'web development'],
29
+ 'views': ['frontend', 'web development'],
30
+ 'hooks': ['frontend', 'react'],
31
+ 'store': ['frontend', 'state management'],
32
+ 'redux': ['frontend', 'state management'],
33
+
34
+ // Backend
35
+ 'server': ['backend', 'api'],
36
+ 'services': ['backend', 'microservices'],
37
+ 'controllers': ['backend', 'api'],
38
+ 'models': ['backend', 'database'],
39
+ 'db': ['database', 'backend'],
40
+ 'database': ['database', 'backend'],
41
+
42
+ // Infrastructure
43
+ 'infra': ['infrastructure', 'devops'],
44
+ 'deploy': ['devops', 'infrastructure'],
45
+ 'docker': ['devops', 'containers'],
46
+ 'k8s': ['devops', 'kubernetes'],
47
+ 'terraform': ['infrastructure', 'devops'],
48
+ 'ci': ['devops', 'automation'],
49
+
50
+ // Testing
51
+ 'tests': ['testing', 'quality'],
52
+ 'test': ['testing', 'quality'],
53
+ '__tests__': ['testing', 'quality'],
54
+ 'spec': ['testing', 'quality'],
55
+
56
+ // Mobile
57
+ 'ios': ['mobile development', 'ios'],
58
+ 'android': ['mobile development', 'android'],
59
+ 'mobile': ['mobile development'],
60
+
61
+ // Data
62
+ 'data': ['data science', 'analytics'],
63
+ 'analytics': ['analytics', 'data science'],
64
+
65
+ // Security
66
+ 'auth': ['security', 'authentication'],
67
+ 'security': ['security'],
68
+
69
+ // Tools
70
+ 'tools': ['developer tools'],
71
+ 'cli': ['developer tools', 'cli'],
72
+ 'scripts': ['developer tools', 'automation'],
73
+
74
+ // Content
75
+ 'docs': ['documentation', 'writing'],
76
+ 'content': ['content creation'],
77
+
78
+ // Games
79
+ 'game': ['game development', 'gaming'],
80
+ 'games': ['game development', 'gaming'],
81
+
82
+ // Design
83
+ 'design': ['design', 'creative'],
84
+ 'styles': ['frontend', 'design'],
85
+ 'ui': ['design', 'frontend']
86
+ };
87
+
88
+ // Map branch topics to interest categories
89
+ const TOPIC_TO_INTERESTS = {
90
+ 'auth': ['security', 'authentication'],
91
+ 'login': ['security', 'authentication'],
92
+ 'signup': ['security', 'authentication'],
93
+ 'user': ['user experience', 'product'],
94
+ 'ui': ['frontend', 'design'],
95
+ 'api': ['backend', 'api'],
96
+ 'db': ['database', 'backend'],
97
+ 'database': ['database', 'backend'],
98
+ 'ml': ['machine learning', 'ai'],
99
+ 'ai': ['ai', 'machine learning'],
100
+ 'agent': ['agents', 'ai', 'automation'],
101
+ 'test': ['testing', 'quality'],
102
+ 'config': ['infrastructure', 'devops'],
103
+ 'payment': ['fintech', 'product'],
104
+ 'email': ['communication', 'backend'],
105
+ 'notification': ['communication', 'product'],
106
+ 'search': ['search', 'backend'],
107
+ 'cache': ['performance', 'backend'],
108
+ 'session': ['backend', 'security'],
109
+ 'deploy': ['devops', 'infrastructure'],
110
+ 'perf': ['performance', 'optimization'],
111
+ 'mobile': ['mobile development'],
112
+ 'social': ['social', 'community']
113
+ };
114
+
115
+ // Map file extensions to interests
116
+ const EXTENSION_TO_INTERESTS = {
117
+ '.py': ['python'],
118
+ '.js': ['javascript'],
119
+ '.ts': ['typescript'],
120
+ '.tsx': ['react', 'typescript'],
121
+ '.jsx': ['react', 'javascript'],
122
+ '.go': ['go'],
123
+ '.rs': ['rust'],
124
+ '.rb': ['ruby'],
125
+ '.java': ['java'],
126
+ '.swift': ['swift', 'ios'],
127
+ '.kt': ['kotlin', 'android'],
128
+ '.sol': ['solidity', 'web3'],
129
+ '.vue': ['vue', 'frontend'],
130
+ '.svelte': ['svelte', 'frontend'],
131
+ '.css': ['css', 'frontend'],
132
+ '.scss': ['css', 'frontend'],
133
+ '.sql': ['sql', 'database'],
134
+ '.prisma': ['database', 'backend'],
135
+ '.graphql': ['graphql', 'api'],
136
+ '.proto': ['grpc', 'backend'],
137
+ '.yml': ['devops', 'infrastructure'],
138
+ '.yaml': ['devops', 'infrastructure'],
139
+ '.tf': ['terraform', 'infrastructure'],
140
+ '.md': ['documentation', 'writing']
141
+ };
142
+
143
+ /**
144
+ * Infer interests from user's current context
145
+ * Returns array of interests with confidence scores and sources
146
+ *
147
+ * @param {Object} context - User's current context
148
+ * @param {string} context.file - Current file path
149
+ * @param {string} context.branch - Current branch name
150
+ * @param {string} context.mood - Current mood/state
151
+ * @param {string} context.error - Current error (if any)
152
+ * @returns {Array} Array of { interest, source, confidence }
153
+ */
154
+ function inferLiveInterests(context) {
155
+ if (!context) return [];
156
+
157
+ const interests = [];
158
+
159
+ // 1. Infer from current file/module
160
+ if (context.file) {
161
+ const module = getModule(context.file);
162
+ if (module && MODULE_TO_INTERESTS[module.toLowerCase()]) {
163
+ const moduleInterests = MODULE_TO_INTERESTS[module.toLowerCase()];
164
+ interests.push(...moduleInterests.map(i => ({
165
+ interest: i,
166
+ source: 'file',
167
+ confidence: 0.7,
168
+ detail: `editing ${module}/`
169
+ })));
170
+ }
171
+
172
+ // Also check file extension
173
+ const ext = getFileExtension(context.file);
174
+ if (ext && EXTENSION_TO_INTERESTS[ext]) {
175
+ interests.push(...EXTENSION_TO_INTERESTS[ext].map(i => ({
176
+ interest: i,
177
+ source: 'extension',
178
+ confidence: 0.5,
179
+ detail: `working with ${ext} files`
180
+ })));
181
+ }
182
+ }
183
+
184
+ // 2. Infer from branch name
185
+ if (context.branch) {
186
+ const topic = extractBranchTopic(context.branch);
187
+ if (topic && TOPIC_TO_INTERESTS[topic]) {
188
+ interests.push(...TOPIC_TO_INTERESTS[topic].map(i => ({
189
+ interest: i,
190
+ source: 'branch',
191
+ confidence: 0.8,
192
+ detail: `on ${context.branch}`
193
+ })));
194
+ }
195
+
196
+ // Special branch patterns
197
+ if (/agent|mcp|claude/i.test(context.branch)) {
198
+ interests.push({
199
+ interest: 'agents',
200
+ source: 'branch',
201
+ confidence: 0.9,
202
+ detail: `agent-related branch`
203
+ });
204
+ }
205
+ }
206
+
207
+ // 3. Infer from activity state (mood)
208
+ if (context.mood) {
209
+ const moodInterests = inferFromMood(context.mood);
210
+ interests.push(...moodInterests);
211
+ }
212
+
213
+ // 4. Infer from error (if debugging)
214
+ if (context.error) {
215
+ const errorInterests = inferFromError(context.error);
216
+ interests.push(...errorInterests);
217
+ }
218
+
219
+ // Dedupe and return top interests by confidence
220
+ return dedupeByConfidence(interests).slice(0, 5);
221
+ }
222
+
223
+ /**
224
+ * Infer interests from mood/state
225
+ */
226
+ function inferFromMood(mood) {
227
+ const interests = [];
228
+ const moodLower = (mood || '').toLowerCase();
229
+
230
+ if (moodLower.includes('shipping') || moodLower.includes('deploy')) {
231
+ interests.push({
232
+ interest: 'shipping',
233
+ source: 'mood',
234
+ confidence: 0.6,
235
+ detail: 'actively shipping'
236
+ });
237
+ }
238
+
239
+ if (moodLower.includes('debug') || moodLower.includes('fix')) {
240
+ interests.push({
241
+ interest: 'debugging',
242
+ source: 'mood',
243
+ confidence: 0.6,
244
+ detail: 'debugging'
245
+ });
246
+ }
247
+
248
+ return interests;
249
+ }
250
+
251
+ /**
252
+ * Infer interests from error messages
253
+ */
254
+ function inferFromError(error) {
255
+ const interests = [];
256
+ const errorLower = (error || '').toLowerCase();
257
+
258
+ // Database errors
259
+ if (/sql|postgres|mysql|mongo|prisma|database/i.test(errorLower)) {
260
+ interests.push({
261
+ interest: 'database',
262
+ source: 'error',
263
+ confidence: 0.5,
264
+ detail: 'debugging database issue'
265
+ });
266
+ }
267
+
268
+ // API errors
269
+ if (/fetch|axios|api|endpoint|401|403|404|500/i.test(errorLower)) {
270
+ interests.push({
271
+ interest: 'api',
272
+ source: 'error',
273
+ confidence: 0.5,
274
+ detail: 'debugging API issue'
275
+ });
276
+ }
277
+
278
+ // Type errors (TypeScript)
279
+ if (/type.*error|typescript|cannot assign/i.test(errorLower)) {
280
+ interests.push({
281
+ interest: 'typescript',
282
+ source: 'error',
283
+ confidence: 0.5,
284
+ detail: 'debugging type error'
285
+ });
286
+ }
287
+
288
+ return interests;
289
+ }
290
+
291
+ /**
292
+ * Get file extension from path
293
+ */
294
+ function getFileExtension(filePath) {
295
+ if (!filePath) return null;
296
+ const match = filePath.match(/\.[^./]+$/);
297
+ return match ? match[0].toLowerCase() : null;
298
+ }
299
+
300
+ /**
301
+ * Deduplicate interests, keeping highest confidence for each
302
+ */
303
+ function dedupeByConfidence(interests) {
304
+ const byInterest = new Map();
305
+
306
+ for (const item of interests) {
307
+ const existing = byInterest.get(item.interest);
308
+ if (!existing || item.confidence > existing.confidence) {
309
+ byInterest.set(item.interest, item);
310
+ }
311
+ }
312
+
313
+ return Array.from(byInterest.values())
314
+ .sort((a, b) => b.confidence - a.confidence);
315
+ }
316
+
317
+ /**
318
+ * Merge live interests with static interests
319
+ * Prioritizes explicit user-set interests, then adds live context
320
+ *
321
+ * @param {Array} staticInterests - User's profile interests
322
+ * @param {Array} liveInterests - Inferred from current context
323
+ * @returns {Array} Combined interests with sources
324
+ */
325
+ function mergeInterests(staticInterests = [], liveInterests = []) {
326
+ const merged = new Map();
327
+
328
+ // Add static interests first (higher priority)
329
+ for (const interest of staticInterests) {
330
+ merged.set(interest, {
331
+ interest,
332
+ source: 'profile',
333
+ confidence: 1.0
334
+ });
335
+ }
336
+
337
+ // Add live interests if not already present
338
+ for (const item of liveInterests) {
339
+ if (!merged.has(item.interest)) {
340
+ merged.set(item.interest, item);
341
+ }
342
+ }
343
+
344
+ return Array.from(merged.values())
345
+ .sort((a, b) => b.confidence - a.confidence);
346
+ }
347
+
348
+ /**
349
+ * Format live interests for display
350
+ */
351
+ function formatLiveInterests(interests) {
352
+ if (!interests || interests.length === 0) return null;
353
+
354
+ const live = interests.filter(i => i.source !== 'profile');
355
+ if (live.length === 0) return null;
356
+
357
+ return live.map(i => i.interest).join(', ');
358
+ }
359
+
360
+ module.exports = {
361
+ inferLiveInterests,
362
+ mergeInterests,
363
+ formatLiveInterests,
364
+ dedupeByConfidence,
365
+ // Expose mappings for testing/extension
366
+ MODULE_TO_INTERESTS,
367
+ TOPIC_TO_INTERESTS,
368
+ EXTENSION_TO_INTERESTS
369
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * MCP `list_changed` notification emitter
3
+ *
4
+ * Triggers Claude to refresh tool results without reconnection.
5
+ * Implements debouncing to prevent notification spam.
6
+ *
7
+ * This eliminates the need for 30-second polling loops,
8
+ * reducing API calls by ~90% and providing instant updates.
9
+ */
10
+
11
+ class NotificationEmitter {
12
+ constructor(server) {
13
+ this.server = server;
14
+ this.debounceTimers = {};
15
+ }
16
+
17
+ /**
18
+ * Emit list_changed notification with debouncing
19
+ * @param {string} reason - Why notification is being sent (for logging/debugging)
20
+ * @param {number} debounceMs - Debounce window in milliseconds (default: 1000ms)
21
+ */
22
+ emitChange(reason, debounceMs = 1000) {
23
+ // Debounce to prevent notification spam
24
+ // If we get multiple changes of the same type within the window,
25
+ // only emit one notification
26
+ if (this.debounceTimers[reason]) {
27
+ clearTimeout(this.debounceTimers[reason]);
28
+ }
29
+
30
+ this.debounceTimers[reason] = setTimeout(() => {
31
+ try {
32
+ this.server.notification({
33
+ method: "notifications/list_changed"
34
+ });
35
+ delete this.debounceTimers[reason];
36
+ } catch (e) {
37
+ // Silent fail - notifications are best-effort
38
+ // If notification fails, Claude will continue working normally
39
+ }
40
+ }, debounceMs);
41
+ }
42
+
43
+ /**
44
+ * Emit immediately without debouncing
45
+ * Use for urgent updates like direct mentions
46
+ */
47
+ emitImmediate() {
48
+ try {
49
+ this.server.notification({
50
+ method: "notifications/list_changed"
51
+ });
52
+ } catch (e) {
53
+ // Silent fail
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Cancel pending notifications for a specific reason
59
+ * Useful when shutting down or cleaning up
60
+ */
61
+ cancel(reason) {
62
+ if (this.debounceTimers[reason]) {
63
+ clearTimeout(this.debounceTimers[reason]);
64
+ delete this.debounceTimers[reason];
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Cancel all pending notifications
70
+ */
71
+ cancelAll() {
72
+ Object.values(this.debounceTimers).forEach(timer => clearTimeout(timer));
73
+ this.debounceTimers = {};
74
+ }
75
+ }
76
+
77
+ module.exports = NotificationEmitter;
package/notify.js CHANGED
@@ -89,6 +89,8 @@ async function checkAndNotify(inbox) {
89
89
 
90
90
  let notified = false;
91
91
 
92
+ const myHandle = config.getHandle();
93
+
92
94
  for (const msg of inbox) {
93
95
  // Skip if already notified
94
96
  if (state.notifiedIds.includes(msg.id)) continue;
@@ -96,6 +98,9 @@ async function checkAndNotify(inbox) {
96
98
  // Skip if already read
97
99
  if (msg.read) continue;
98
100
 
101
+ // Skip messages FROM yourself (don't notify about your own sends)
102
+ if (myHandle && msg.from?.toLowerCase() === myHandle.toLowerCase()) continue;
103
+
99
104
  const msgTime = new Date(msg.createdAt).getTime();
100
105
  const age = now - msgTime;
101
106
 
@@ -104,7 +109,6 @@ async function checkAndNotify(inbox) {
104
109
  let reason = '';
105
110
 
106
111
  // Rule 1: Direct mention in message (always if not "off")
107
- const myHandle = config.getHandle();
108
112
  if (myHandle && msg.text && msg.text.toLowerCase().includes(`@${myHandle}`)) {
109
113
  shouldNotify = true;
110
114
  reason = 'mention';
package/package.json CHANGED
@@ -1,31 +1,28 @@
1
1
  {
2
2
  "name": "slashvibe-mcp",
3
- "version": "0.3.20",
4
- "mcpName": "io.github.VibeCodingInc/vibe-mcp",
5
- "description": "Social MCP server - DMs, presence, discovery, and games for AI-assisted developers. Works with Claude Code, Cursor, VS Code, Windsurf, and any MCP client.",
3
+ "version": "0.3.21",
4
+ "mcpName": "io.github.vibecodinginc/vibe",
5
+ "description": "Social layer for Claude Code - DMs, presence, and connection between AI-assisted developers",
6
6
  "main": "index.js",
7
7
  "bin": {
8
- "slashvibe-mcp": "./index.js"
8
+ "slashvibe-mcp": "./index.js",
9
+ "vibe-setup": "./setup.js"
9
10
  },
10
11
  "scripts": {
11
- "start": "node index.js"
12
+ "start": "node index.js",
13
+ "test": "node --test tools/*.test.js",
14
+ "test:context": "node --test tools/_work-context.test.js",
15
+ "lint": "eslint . --ext .js",
16
+ "lint:fix": "eslint . --ext .js --fix"
12
17
  },
13
18
  "keywords": [
14
19
  "mcp",
15
- "mcp-server",
16
- "model-context-protocol",
17
20
  "claude-code",
18
- "cursor",
19
- "windsurf",
20
- "vscode",
21
21
  "vibe",
22
22
  "social",
23
23
  "developer-tools",
24
24
  "ai",
25
- "anthropic",
26
- "messaging",
27
- "presence",
28
- "multiplayer"
25
+ "anthropic"
29
26
  ],
30
27
  "author": "Seth Goldstein <seth@slashvibe.dev>",
31
28
  "license": "MIT",
@@ -42,11 +39,17 @@
42
39
  },
43
40
  "files": [
44
41
  "index.js",
42
+ "setup.js",
43
+ "auth-store.js",
44
+ "auto-update.js",
45
45
  "config.js",
46
46
  "crypto.js",
47
47
  "discord.js",
48
48
  "memory.js",
49
49
  "notify.js",
50
+ "notification-emitter.js",
51
+ "analytics.js",
52
+ "smart-inbox.js",
50
53
  "presence.js",
51
54
  "prompts.js",
52
55
  "twitter.js",
@@ -55,10 +58,12 @@
55
58
  "store/",
56
59
  "protocol/",
57
60
  "intelligence/",
61
+ "games/",
62
+ "bridges/",
58
63
  "README.md"
59
64
  ],
60
65
  "dependencies": {
61
- "better-sqlite3": "^11.0.0",
62
- "crossword-layout-generator": "^0.1.1"
66
+ "crossword-layout-generator": "^0.1.1",
67
+ "node-fetch": "^2.7.0"
63
68
  }
64
69
  }
package/prompts.js CHANGED
@@ -68,7 +68,7 @@ function extractPatterns() {
68
68
 
69
69
  for (const p of prompts) {
70
70
  // Normalize: lowercase, replace @handles with @*, replace quoted strings with "*"
71
- let normalized = p.prompt.toLowerCase()
71
+ const normalized = p.prompt.toLowerCase()
72
72
  .replace(/@\w+/g, '@*')
73
73
  .replace(/"[^"]+"/g, '"*"')
74
74
  .replace(/'[^']+'/g, "'*'")
package/protocol/index.js CHANGED
@@ -307,6 +307,8 @@ function formatPayload(payload) {
307
307
  return formatAckPayload(payload);
308
308
  case 'artifact':
309
309
  return formatArtifactPayload(payload);
310
+ case 'code':
311
+ return formatCodePayload(payload);
310
312
  default:
311
313
  return `📦 _${payload.type} payload_`;
312
314
  }
@@ -357,6 +359,74 @@ function formatAckPayload(payload) {
357
359
  return `${icon} Acknowledged: ${payload.replyTo} (${status})`;
358
360
  }
359
361
 
362
+ /**
363
+ * Format a code snippet payload for display
364
+ */
365
+ function formatCodePayload(payload) {
366
+ const lang = payload.language || '';
367
+ const filename = payload.filename || null;
368
+ const code = payload.code || '';
369
+ const description = payload.description || null;
370
+
371
+ let display = '📝 **Code Snippet**';
372
+ if (filename) {
373
+ display += ` — \`${filename}\``;
374
+ }
375
+ if (lang) {
376
+ display += ` (${lang})`;
377
+ }
378
+ display += '\n';
379
+
380
+ if (description) {
381
+ display += `> ${description}\n`;
382
+ }
383
+
384
+ display += `\`\`\`${lang}\n${code}\n\`\`\``;
385
+
386
+ return display;
387
+ }
388
+
389
+ /**
390
+ * Create a code snippet payload
391
+ * @param {string} code - The code content
392
+ * @param {object} options - Options
393
+ * @param {string} [options.language] - Programming language
394
+ * @param {string} [options.filename] - Original filename
395
+ * @param {string} [options.description] - Brief description
396
+ */
397
+ function createCodePayload(code, options = {}) {
398
+ return {
399
+ type: 'code',
400
+ version: PROTOCOL_VERSION,
401
+ code,
402
+ language: options.language || detectLanguage(code),
403
+ filename: options.filename || null,
404
+ description: options.description || null,
405
+ };
406
+ }
407
+
408
+ /**
409
+ * Simple language detection based on content patterns
410
+ */
411
+ function detectLanguage(code) {
412
+ if (!code) return '';
413
+
414
+ // Check for common patterns
415
+ if (code.includes('import React') || code.includes('useState') || code.includes('useEffect')) return 'jsx';
416
+ if (code.includes('import ') && code.includes(' from ')) return 'javascript';
417
+ if (code.includes('async function') || code.includes('await ')) return 'javascript';
418
+ if (code.includes('def ') && code.includes(':')) return 'python';
419
+ if (code.includes('func ') && code.includes('()')) return 'go';
420
+ if (code.includes('fn ') && code.includes('->')) return 'rust';
421
+ if (code.includes('SELECT ') || code.includes('INSERT INTO')) return 'sql';
422
+ if (code.includes('<!DOCTYPE') || code.includes('<html')) return 'html';
423
+ if (code.includes('{') && code.includes(':') && code.includes(';')) return 'css';
424
+ if (code.startsWith('{') && code.endsWith('}')) return 'json';
425
+ if (code.startsWith('#!') && code.includes('/bin/')) return 'bash';
426
+
427
+ return '';
428
+ }
429
+
360
430
  function formatArtifactPayload(payload) {
361
431
  const template = payload.template || 'artifact';
362
432
  const templateIcon = template === 'guide' ? '📘' : template === 'learning' ? '💡' : template === 'workspace' ? '🗂️' : '📦';
@@ -422,6 +492,9 @@ module.exports = {
422
492
  // Artifact helpers
423
493
  createArtifactPayload,
424
494
 
495
+ // Code helpers
496
+ createCodePayload,
497
+
425
498
  // Schemas (for extension)
426
499
  SCHEMAS
427
500
  };