slashvibe-mcp 0.3.21 → 0.3.22

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 (229) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +280 -47
  3. package/config.js +36 -31
  4. package/crypto.js +1 -6
  5. package/discord.js +19 -19
  6. package/index.js +217 -207
  7. package/intelligence/index.js +2 -9
  8. package/intelligence/infer.js +10 -16
  9. package/intelligence/patterns.js +23 -18
  10. package/intelligence/proactive.js +16 -15
  11. package/intelligence/serendipity.js +57 -20
  12. package/memory.js +13 -8
  13. package/notify.js +39 -14
  14. package/package.json +27 -20
  15. package/presence.js +2 -2
  16. package/prompts.js +5 -9
  17. package/protocol/index.js +123 -87
  18. package/protocol/telegram-commands.js +36 -37
  19. package/store/api.js +358 -529
  20. package/store/local.js +9 -10
  21. package/store/profiles.js +48 -192
  22. package/store/reservations.js +2 -9
  23. package/store/skills.js +69 -71
  24. package/store/sqlite.js +355 -0
  25. package/tools/_actions.js +48 -387
  26. package/tools/_connection-queue.js +45 -56
  27. package/tools/_discovery-enhanced.js +52 -57
  28. package/tools/_discovery.js +87 -185
  29. package/tools/{l2-status.js → _experimental/l2-status.js} +68 -70
  30. package/tools/{shipback.js → _experimental/shipback.js} +4 -3
  31. package/tools/_proactive-discovery.js +60 -73
  32. package/tools/_shared/index.js +41 -64
  33. package/tools/admin-inbox.js +10 -15
  34. package/tools/agents.js +1 -1
  35. package/tools/artifact-create.js +13 -23
  36. package/tools/artifact-view.js +4 -4
  37. package/tools/{_deprecated/back.js → back.js} +1 -1
  38. package/tools/bye.js +3 -5
  39. package/tools/consent.js +2 -2
  40. package/tools/context.js +9 -10
  41. package/tools/crossword.js +3 -2
  42. package/tools/discover.js +94 -356
  43. package/tools/dm.js +27 -86
  44. package/tools/doctor.js +12 -41
  45. package/tools/drawing.js +34 -20
  46. package/tools/echo.js +11 -11
  47. package/tools/feed.js +30 -58
  48. package/tools/follow.js +64 -187
  49. package/tools/{_deprecated/forget.js → forget.js} +4 -7
  50. package/tools/game.js +144 -48
  51. package/tools/handoff.js +6 -8
  52. package/tools/help.js +3 -3
  53. package/tools/idea.js +15 -27
  54. package/tools/inbox.js +121 -293
  55. package/tools/init.js +54 -151
  56. package/tools/invite.js +8 -21
  57. package/tools/migrate.js +27 -24
  58. package/tools/multiplayer-game.js +50 -40
  59. package/tools/{_deprecated/mute.js → mute.js} +4 -3
  60. package/tools/notifications.js +58 -48
  61. package/tools/observe.js +12 -15
  62. package/tools/onboarding.js +8 -11
  63. package/tools/open.js +13 -144
  64. package/tools/party-game.js +23 -12
  65. package/tools/patterns.js +2 -1
  66. package/tools/ping.js +5 -7
  67. package/tools/react.js +28 -30
  68. package/tools/{_deprecated/recall.js → recall.js} +5 -10
  69. package/tools/release.js +4 -2
  70. package/tools/{_deprecated/remember.js → remember.js} +4 -6
  71. package/tools/report.js +2 -2
  72. package/tools/request.js +6 -26
  73. package/tools/reserve.js +1 -1
  74. package/tools/session-fork.js +97 -0
  75. package/tools/session-save.js +109 -0
  76. package/tools/settings.js +30 -99
  77. package/tools/ship.js +74 -56
  78. package/tools/{_deprecated/skills-exchange.js → skills-exchange.js} +38 -39
  79. package/tools/social-inbox.js +22 -28
  80. package/tools/social-post.js +24 -27
  81. package/tools/solo-game.js +54 -46
  82. package/tools/start.js +14 -148
  83. package/tools/status.js +21 -68
  84. package/tools/submit.js +4 -2
  85. package/tools/suggest-tags.js +36 -33
  86. package/tools/summarize.js +19 -16
  87. package/tools/tag-suggestions.js +72 -73
  88. package/tools/test.js +1 -1
  89. package/tools/{_deprecated/tictactoe.js → tictactoe.js} +26 -26
  90. package/tools/token.js +4 -4
  91. package/tools/update.js +1 -2
  92. package/tools/watch.js +132 -112
  93. package/tools/who.js +20 -40
  94. package/tools/{_deprecated/wordassociation.js → wordassociation.js} +23 -20
  95. package/tools/workshop-buddy.js +52 -53
  96. package/tools/x-mentions.js +0 -1
  97. package/tools/x-reply.js +0 -1
  98. package/twitter.js +14 -20
  99. package/version.json +8 -10
  100. package/analytics.js +0 -107
  101. package/auth-store.js +0 -148
  102. package/auto-update.js +0 -130
  103. package/bridges/bridge-monitor.js +0 -388
  104. package/bridges/discord-bot.js +0 -431
  105. package/bridges/farcaster.js +0 -299
  106. package/bridges/telegram.js +0 -261
  107. package/bridges/webhook-health.js +0 -420
  108. package/bridges/webhook-server.js +0 -437
  109. package/bridges/whatsapp.js +0 -441
  110. package/bridges/x-webhook.js +0 -423
  111. package/games/arcade.js +0 -406
  112. package/games/chess.js +0 -451
  113. package/games/colorguess.js +0 -343
  114. package/games/crossword-words.js +0 -171
  115. package/games/crossword.js +0 -461
  116. package/games/drawing.js +0 -347
  117. package/games/gameroulette.js +0 -300
  118. package/games/gamerouter.js +0 -336
  119. package/games/gamestatus.js +0 -337
  120. package/games/guessnumber.js +0 -209
  121. package/games/hangman.js +0 -279
  122. package/games/memory.js +0 -338
  123. package/games/multiplayer-tictactoe.js +0 -389
  124. package/games/pixelart.js +0 -399
  125. package/games/quickduel.js +0 -354
  126. package/games/riddle.js +0 -371
  127. package/games/rockpaperscissors.js +0 -291
  128. package/games/snake.js +0 -406
  129. package/games/storybuilder.js +0 -343
  130. package/games/tictactoe.js +0 -345
  131. package/games/twentyquestions.js +0 -286
  132. package/games/twotruths.js +0 -207
  133. package/games/werewolf.js +0 -508
  134. package/games/wordassociation.js +0 -247
  135. package/games/wordchain.js +0 -135
  136. package/intelligence/interests.js +0 -369
  137. package/notification-emitter.js +0 -77
  138. package/setup.js +0 -480
  139. package/smart-inbox.js +0 -276
  140. package/tools/_deprecated/auto-suggest-connections.js +0 -304
  141. package/tools/_deprecated/bootstrap-skills.js +0 -231
  142. package/tools/_deprecated/bridge-dashboard.js +0 -342
  143. package/tools/_deprecated/bridge-health.js +0 -400
  144. package/tools/_deprecated/bridge-live.js +0 -384
  145. package/tools/_deprecated/bridges.js +0 -383
  146. package/tools/_deprecated/colorguess.js +0 -281
  147. package/tools/_deprecated/discover-insights.js +0 -379
  148. package/tools/_deprecated/discover-momentum.js +0 -256
  149. package/tools/_deprecated/discovery-analytics.js +0 -345
  150. package/tools/_deprecated/discovery-auto-suggest.js +0 -275
  151. package/tools/_deprecated/discovery-bootstrap.js +0 -267
  152. package/tools/_deprecated/discovery-daily.js +0 -375
  153. package/tools/_deprecated/discovery-dashboard.js +0 -385
  154. package/tools/_deprecated/discovery-digest.js +0 -314
  155. package/tools/_deprecated/discovery-hub.js +0 -357
  156. package/tools/_deprecated/discovery-insights.js +0 -384
  157. package/tools/_deprecated/discovery-momentum.js +0 -281
  158. package/tools/_deprecated/discovery-monitor.js +0 -319
  159. package/tools/_deprecated/discovery-proactive.js +0 -300
  160. package/tools/_deprecated/draw.js +0 -317
  161. package/tools/_deprecated/farcaster.js +0 -307
  162. package/tools/_deprecated/games-catalog.js +0 -376
  163. package/tools/_deprecated/games.js +0 -313
  164. package/tools/_deprecated/guessnumber.js +0 -194
  165. package/tools/_deprecated/hangman.js +0 -129
  166. package/tools/_deprecated/multiplayer-tictactoe.js +0 -303
  167. package/tools/_deprecated/riddle.js +0 -240
  168. package/tools/_deprecated/run-bootstrap.js +0 -69
  169. package/tools/_deprecated/skills-analytics.js +0 -349
  170. package/tools/_deprecated/skills-bootstrap.js +0 -301
  171. package/tools/_deprecated/skills-dashboard.js +0 -268
  172. package/tools/_deprecated/skills.js +0 -380
  173. package/tools/_deprecated/smart-intro.js +0 -353
  174. package/tools/_deprecated/storybuilder.js +0 -331
  175. package/tools/_deprecated/telegram-bot.js +0 -183
  176. package/tools/_deprecated/telegram-setup.js +0 -214
  177. package/tools/_deprecated/twentyquestions.js +0 -143
  178. package/tools/_shared.js +0 -234
  179. package/tools/_work-context.js +0 -338
  180. package/tools/_work-context.manual-test.js +0 -199
  181. package/tools/_work-context.test.js +0 -260
  182. package/tools/activity.js +0 -220
  183. package/tools/agent-treasury.js +0 -288
  184. package/tools/analytics.js +0 -191
  185. package/tools/approve.js +0 -197
  186. package/tools/arcade.js +0 -173
  187. package/tools/artifacts-price.js +0 -107
  188. package/tools/ask-expert.js +0 -160
  189. package/tools/available.js +0 -120
  190. package/tools/become-expert.js +0 -150
  191. package/tools/broadcast.js +0 -325
  192. package/tools/chat.js +0 -202
  193. package/tools/collaborative-drawing.js +0 -286
  194. package/tools/connection-status.js +0 -178
  195. package/tools/earnings.js +0 -126
  196. package/tools/friends.js +0 -207
  197. package/tools/genesis.js +0 -233
  198. package/tools/gig-browse.js +0 -206
  199. package/tools/gig-complete.js +0 -144
  200. package/tools/health.js +0 -87
  201. package/tools/leaderboard.js +0 -117
  202. package/tools/lib/git-apply.js +0 -206
  203. package/tools/lib/git-bundle.js +0 -407
  204. package/tools/mint.js +0 -377
  205. package/tools/plan.js +0 -225
  206. package/tools/profile.js +0 -219
  207. package/tools/proof-of-work.js +0 -144
  208. package/tools/pulse.js +0 -218
  209. package/tools/reply.js +0 -166
  210. package/tools/reputation.js +0 -175
  211. package/tools/schedule.js +0 -367
  212. package/tools/search-messages.js +0 -123
  213. package/tools/session.js +0 -467
  214. package/tools/session_price.js +0 -128
  215. package/tools/smart-check.js +0 -201
  216. package/tools/social-processor.js +0 -445
  217. package/tools/streak.js +0 -147
  218. package/tools/stuck.js +0 -297
  219. package/tools/subscribe.js +0 -148
  220. package/tools/subscriptions.js +0 -134
  221. package/tools/tip.js +0 -193
  222. package/tools/wallet.js +0 -269
  223. package/tools/webhook-test.js +0 -388
  224. package/tools/withdraw.js +0 -145
  225. package/tools/work-summary.js +0 -96
  226. package/tools/workshop.js +0 -327
  227. /package/tools/{l2-bridge.js → _experimental/l2-bridge.js} +0 -0
  228. /package/tools/{l2.js → _experimental/l2.js} +0 -0
  229. /package/tools/{_deprecated/away.js → away.js} +0 -0
@@ -15,17 +15,17 @@ const fetch = require('node-fetch');
15
15
  // Chain configuration (matches lib/vibe-l2/config.js)
16
16
  const CHAINS = {
17
17
  'vibe-l2': {
18
- chainId: 84532000, // Mainnet
18
+ chainId: 84532000, // Mainnet
19
19
  name: 'VIBE L2',
20
20
  rpcUrl: process.env.VIBE_L2_RPC || 'https://rpc.vibe.network',
21
- explorerUrl: 'https://explorer.vibe.network',
21
+ explorerUrl: 'https://explorer.vibe.network'
22
22
  },
23
23
  'vibe-l2-testnet': {
24
- chainId: 84532001, // Testnet
24
+ chainId: 84532001, // Testnet
25
25
  name: 'VIBE L2 Testnet',
26
26
  rpcUrl: process.env.VIBE_L2_TESTNET_RPC || 'https://rpc-testnet.vibe.network',
27
- explorerUrl: 'https://explorer-testnet.vibe.network',
28
- },
27
+ explorerUrl: 'https://explorer-testnet.vibe.network'
28
+ }
29
29
  };
30
30
 
31
31
  const definition = {
@@ -48,60 +48,60 @@ const definition = {
48
48
 
49
49
  async function handler(args) {
50
50
  const { network = 'vibe-l2-testnet', verbose = false } = args;
51
- try {
52
- const chain = CHAINS[network];
53
- if (!chain) {
54
- return {
55
- success: false,
56
- error: `Unknown network: ${network}`,
57
- available: Object.keys(CHAINS)
58
- };
59
- }
51
+ try {
52
+ const chain = CHAINS[network];
53
+ if (!chain) {
54
+ return {
55
+ success: false,
56
+ error: `Unknown network: ${network}`,
57
+ available: Object.keys(CHAINS)
58
+ };
59
+ }
60
60
 
61
- // Check RPC health
62
- const rpcStatus = await checkRpcHealth(chain.rpcUrl);
61
+ // Check RPC health
62
+ const rpcStatus = await checkRpcHealth(chain.rpcUrl);
63
63
 
64
- // Get contract addresses from environment
65
- // Standardized format: VIBE_ARTIFACTS_TESTNET or VIBE_ARTIFACTS_MAINNET
66
- const envSuffix = network === 'vibe-l2' ? 'MAINNET' : 'TESTNET';
67
- const contracts = {
68
- vibeArtifacts: process.env[`VIBE_ARTIFACTS_${envSuffix}`],
69
- shipbackRegistry: process.env[`SHIPBACK_REGISTRY_${envSuffix}`],
70
- vibeToken: process.env[`VIBE_TOKEN_${envSuffix}`],
71
- };
64
+ // Get contract addresses from environment
65
+ // Standardized format: VIBE_ARTIFACTS_TESTNET or VIBE_ARTIFACTS_MAINNET
66
+ const envSuffix = network === 'vibe-l2' ? 'MAINNET' : 'TESTNET';
67
+ const contracts = {
68
+ vibeArtifacts: process.env[`VIBE_ARTIFACTS_${envSuffix}`],
69
+ shipbackRegistry: process.env[`SHIPBACK_REGISTRY_${envSuffix}`],
70
+ vibeToken: process.env[`VIBE_TOKEN_${envSuffix}`]
71
+ };
72
72
 
73
- // Check Shipback API health
74
- const apiUrl = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
75
- let apiHealth = { status: 'unknown' };
76
- try {
77
- const apiResp = await fetch(`${apiUrl}/api/health?full=true`, { timeout: 5000 });
78
- apiHealth = await apiResp.json();
79
- } catch (e) {
80
- apiHealth = { status: 'error', error: e.message };
81
- }
73
+ // Check Shipback API health
74
+ const apiUrl = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
75
+ let apiHealth = { status: 'unknown' };
76
+ try {
77
+ const apiResp = await fetch(`${apiUrl}/api/health?full=true`, { timeout: 5000 });
78
+ apiHealth = await apiResp.json();
79
+ } catch (e) {
80
+ apiHealth = { status: 'error', error: e.message };
81
+ }
82
82
 
83
- // Build status report
84
- const status = {
85
- network: chain.name,
86
- chainId: chain.chainId,
87
- rpc: {
88
- url: chain.rpcUrl,
89
- ...rpcStatus
90
- },
91
- contracts: {
92
- vibeArtifacts: contracts.vibeArtifacts || 'not deployed',
93
- shipbackRegistry: contracts.shipbackRegistry || 'not deployed',
94
- vibeToken: contracts.vibeToken || 'not deployed (optional)',
95
- },
96
- api: apiHealth.status === 'healthy' ? 'healthy' : apiHealth.status,
97
- explorer: chain.explorerUrl
98
- };
83
+ // Build status report
84
+ const status = {
85
+ network: chain.name,
86
+ chainId: chain.chainId,
87
+ rpc: {
88
+ url: chain.rpcUrl,
89
+ ...rpcStatus
90
+ },
91
+ contracts: {
92
+ vibeArtifacts: contracts.vibeArtifacts || 'not deployed',
93
+ shipbackRegistry: contracts.shipbackRegistry || 'not deployed',
94
+ vibeToken: contracts.vibeToken || 'not deployed (optional)'
95
+ },
96
+ api: apiHealth.status === 'healthy' ? 'healthy' : apiHealth.status,
97
+ explorer: chain.explorerUrl
98
+ };
99
99
 
100
- // Format output
101
- const rpcIcon = rpcStatus.healthy ? '🟢' : '🔴';
102
- const apiIcon = apiHealth.status === 'healthy' ? '🟢' : '🟡';
100
+ // Format output
101
+ const rpcIcon = rpcStatus.healthy ? '🟢' : '🔴';
102
+ const apiIcon = apiHealth.status === 'healthy' ? '🟢' : '🟡';
103
103
 
104
- let formatted = `
104
+ let formatted = `
105
105
  ┌─────────────────────────────────────────────────────┐
106
106
  │ VIBE L2 STATUS │
107
107
  ├─────────────────────────────────────────────────────┤
@@ -121,28 +121,27 @@ async function handler(args) {
121
121
  └─────────────────────────────────────────────────────┘
122
122
  `.trim();
123
123
 
124
- if (verbose && rpcStatus.healthy) {
125
- formatted += `\n\nDetailed RPC Info:
124
+ if (verbose && rpcStatus.healthy) {
125
+ formatted += `\n\nDetailed RPC Info:
126
126
  Latest Block: ${rpcStatus.blockNumber}
127
127
  Block Time: ${rpcStatus.blockTime || 'unknown'}
128
128
  Gas Price: ${rpcStatus.gasPrice} gwei
129
129
  Chain ID: ${rpcStatus.chainId}`;
130
- }
131
-
132
- // Shipback info
133
- formatted += `\n\n💰 Shipback Distribution: 80% creator / 15% protocol / 5% foundation`;
134
- formatted += `\n📖 Explorer: ${chain.explorerUrl}`;
130
+ }
135
131
 
136
- return {
137
- display: formatted,
138
- data: status
139
- };
132
+ // Shipback info
133
+ formatted += `\n\n💰 Shipback Distribution: 80% creator / 15% protocol / 5% foundation`;
134
+ formatted += `\n📖 Explorer: ${chain.explorerUrl}`;
140
135
 
141
- } catch (error) {
142
- return {
143
- display: `❌ Failed to check L2 status: ${error.message}`
144
- };
145
- }
136
+ return {
137
+ display: formatted,
138
+ data: status
139
+ };
140
+ } catch (error) {
141
+ return {
142
+ display: `❌ Failed to check L2 status: ${error.message}`
143
+ };
144
+ }
146
145
  }
147
146
 
148
147
  /**
@@ -205,7 +204,6 @@ async function checkRpcHealth(rpcUrl) {
205
204
  chainId,
206
205
  latency: 'ok'
207
206
  };
208
-
209
207
  } catch (error) {
210
208
  return {
211
209
  healthy: false,
@@ -253,9 +253,10 @@ async function initiateClaim(apiUrl, handle, signature, nonce) {
253
253
  const balanceData = await balanceResp.json();
254
254
 
255
255
  if (!balanceData.canClaim) {
256
- const reason = parseFloat(balanceData.pendingBalanceEth) < parseFloat(balanceData.minClaimAmountEth)
257
- ? `Balance (${balanceData.pendingBalanceEth} ETH) below minimum (${balanceData.minClaimAmountEth} ETH)`
258
- : `Cooldown active until ${balanceData.cooldownEndsAt}`;
256
+ const reason =
257
+ parseFloat(balanceData.pendingBalanceEth) < parseFloat(balanceData.minClaimAmountEth)
258
+ ? `Balance (${balanceData.pendingBalanceEth} ETH) below minimum (${balanceData.minClaimAmountEth} ETH)`
259
+ : `Cooldown active until ${balanceData.cooldownEndsAt}`;
259
260
 
260
261
  return {
261
262
  display: `❌ Cannot claim yet: ${reason}
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Proactive Discovery Engine — Smart background matching for offline users
3
- *
3
+ *
4
4
  * This runs when users aren't online to:
5
5
  * - Analyze recent ships and suggest collaborations
6
6
  * - Pre-compute high-quality matches for when users return
@@ -16,19 +16,19 @@ const { formatTimeAgo } = require('./_shared');
16
16
  async function analyzeShippingPatterns() {
17
17
  const profiles = await userProfiles.getAllProfiles();
18
18
  const now = Date.now();
19
- const twoWeeksAgo = now - (14 * 24 * 60 * 60 * 1000);
20
-
19
+ const twoWeeksAgo = now - 14 * 24 * 60 * 60 * 1000;
20
+
21
21
  const patterns = {
22
22
  recentShippers: [],
23
23
  complementaryShips: [],
24
24
  similarProjects: [],
25
25
  emergingTrends: {}
26
26
  };
27
-
27
+
28
28
  // Find users who shipped recently
29
29
  for (const profile of profiles) {
30
30
  if (!profile.ships || profile.ships.length === 0) continue;
31
-
31
+
32
32
  const recentShips = profile.ships.filter(s => s.timestamp > twoWeeksAgo);
33
33
  if (recentShips.length > 0) {
34
34
  patterns.recentShippers.push({
@@ -40,7 +40,7 @@ async function analyzeShippingPatterns() {
40
40
  });
41
41
  }
42
42
  }
43
-
43
+
44
44
  // Find complementary ships (e.g., one shipped frontend, another backend)
45
45
  const complementaryPairs = [
46
46
  { keywords: ['frontend', 'ui', 'react', 'vue'], complement: ['backend', 'api', 'server', 'database'] },
@@ -49,24 +49,16 @@ async function analyzeShippingPatterns() {
49
49
  { keywords: ['mobile', 'app', 'ios', 'android'], complement: ['backend', 'api'] },
50
50
  { keywords: ['smart contract', 'solidity', 'web3'], complement: ['frontend', 'dapp'] }
51
51
  ];
52
-
52
+
53
53
  for (const pair of complementaryPairs) {
54
- const primaryShippers = patterns.recentShippers.filter(s =>
55
- s.ships.some(ship =>
56
- pair.keywords.some(keyword =>
57
- ship.what.toLowerCase().includes(keyword)
58
- )
59
- )
54
+ const primaryShippers = patterns.recentShippers.filter(s =>
55
+ s.ships.some(ship => pair.keywords.some(keyword => ship.what.toLowerCase().includes(keyword)))
60
56
  );
61
-
62
- const complementShippers = patterns.recentShippers.filter(s =>
63
- s.ships.some(ship =>
64
- pair.complement.some(keyword =>
65
- ship.what.toLowerCase().includes(keyword)
66
- )
67
- )
57
+
58
+ const complementShippers = patterns.recentShippers.filter(s =>
59
+ s.ships.some(ship => pair.complement.some(keyword => ship.what.toLowerCase().includes(keyword)))
68
60
  );
69
-
61
+
70
62
  if (primaryShippers.length > 0 && complementShippers.length > 0) {
71
63
  patterns.complementaryShips.push({
72
64
  type: `${pair.keywords[0]}/${pair.complement[0]}`,
@@ -75,15 +67,17 @@ async function analyzeShippingPatterns() {
75
67
  });
76
68
  }
77
69
  }
78
-
70
+
79
71
  // Find similar project patterns
80
72
  const projectKeywords = {};
81
73
  for (const shipper of patterns.recentShippers) {
82
74
  for (const ship of shipper.ships) {
83
- const words = ship.what.toLowerCase().split(/\s+/)
75
+ const words = ship.what
76
+ .toLowerCase()
77
+ .split(/\s+/)
84
78
  .filter(word => word.length > 3)
85
79
  .filter(word => !['the', 'and', 'for', 'with', 'that', 'this'].includes(word));
86
-
80
+
87
81
  for (const word of words) {
88
82
  if (!projectKeywords[word]) projectKeywords[word] = [];
89
83
  projectKeywords[word].push({
@@ -94,7 +88,7 @@ async function analyzeShippingPatterns() {
94
88
  }
95
89
  }
96
90
  }
97
-
91
+
98
92
  // Find keywords with multiple recent ships (emerging trends)
99
93
  for (const [keyword, ships] of Object.entries(projectKeywords)) {
100
94
  if (ships.length >= 2 && keyword !== 'app' && keyword !== 'website') {
@@ -104,7 +98,7 @@ async function analyzeShippingPatterns() {
104
98
  };
105
99
  }
106
100
  }
107
-
101
+
108
102
  return patterns;
109
103
  }
110
104
 
@@ -112,36 +106,31 @@ async function analyzeShippingPatterns() {
112
106
  async function preComputeMatches() {
113
107
  const profiles = await userProfiles.getAllProfiles();
114
108
  const now = Date.now();
115
- const oneWeekAgo = now - (7 * 24 * 60 * 60 * 1000);
116
-
109
+ const oneWeekAgo = now - 7 * 24 * 60 * 60 * 1000;
110
+
117
111
  // Find users who haven't been active but have good profiles
118
- const dormantUsers = profiles.filter(p =>
119
- p.lastSeen && p.lastSeen < oneWeekAgo &&
120
- p.building && p.interests?.length > 0
121
- );
122
-
123
- const activeUsers = profiles.filter(p =>
124
- p.lastSeen && p.lastSeen > oneWeekAgo
112
+ const dormantUsers = profiles.filter(
113
+ p => p.lastSeen && p.lastSeen < oneWeekAgo && p.building && p.interests?.length > 0
125
114
  );
126
-
115
+
116
+ const activeUsers = profiles.filter(p => p.lastSeen && p.lastSeen > oneWeekAgo);
117
+
127
118
  const preComputedMatches = [];
128
-
119
+
129
120
  for (const dormantUser of dormantUsers) {
130
121
  const potentialMatches = [];
131
-
122
+
132
123
  for (const activeUser of activeUsers) {
133
124
  if (dormantUser.handle === activeUser.handle) continue;
134
-
125
+
135
126
  // Check if already connected
136
- const alreadyConnected = await userProfiles.hasBeenConnected(
137
- dormantUser.handle,
138
- activeUser.handle
139
- );
127
+ const alreadyConnected = await userProfiles.hasBeenConnected(dormantUser.handle, activeUser.handle);
140
128
  if (alreadyConnected) continue;
141
-
129
+
142
130
  // Calculate match score using existing algorithm
143
131
  const match = calculateSimpleMatchScore(dormantUser, activeUser);
144
- if (match.score > 15) { // Higher threshold for dormant users
132
+ if (match.score > 15) {
133
+ // Higher threshold for dormant users
145
134
  potentialMatches.push({
146
135
  handle: activeUser.handle,
147
136
  score: match.score,
@@ -150,7 +139,7 @@ async function preComputeMatches() {
150
139
  });
151
140
  }
152
141
  }
153
-
142
+
154
143
  if (potentialMatches.length > 0) {
155
144
  preComputedMatches.push({
156
145
  dormantUser: dormantUser.handle,
@@ -159,7 +148,7 @@ async function preComputeMatches() {
159
148
  });
160
149
  }
161
150
  }
162
-
151
+
163
152
  return preComputedMatches;
164
153
  }
165
154
 
@@ -167,7 +156,7 @@ async function preComputeMatches() {
167
156
  function calculateSimpleMatchScore(user1, user2) {
168
157
  let score = 0;
169
158
  const reasons = [];
170
-
159
+
171
160
  // Interest overlap
172
161
  if (user1.interests && user2.interests) {
173
162
  const shared = user1.interests.filter(i => user2.interests.includes(i));
@@ -176,8 +165,8 @@ function calculateSimpleMatchScore(user1, user2) {
176
165
  reasons.push(`Shared interests: ${shared.slice(0, 2).join(', ')}`);
177
166
  }
178
167
  }
179
-
180
- // Tag overlap
168
+
169
+ // Tag overlap
181
170
  if (user1.tags && user2.tags) {
182
171
  const sharedTags = user1.tags.filter(t => user2.tags.includes(t));
183
172
  if (sharedTags.length > 0) {
@@ -185,7 +174,7 @@ function calculateSimpleMatchScore(user1, user2) {
185
174
  reasons.push(`Similar skills: ${sharedTags.slice(0, 2).join(', ')}`);
186
175
  }
187
176
  }
188
-
177
+
189
178
  // Building similarity
190
179
  if (user1.building && user2.building) {
191
180
  const building1 = user1.building.toLowerCase();
@@ -198,7 +187,7 @@ function calculateSimpleMatchScore(user1, user2) {
198
187
  reasons.push(`Both working on ${overlap[0]}`);
199
188
  }
200
189
  }
201
-
190
+
202
191
  return { score, reasons: reasons.slice(0, 2) };
203
192
  }
204
193
 
@@ -206,21 +195,21 @@ function calculateSimpleMatchScore(user1, user2) {
206
195
  async function identifyEmergingClusters() {
207
196
  const profiles = await userProfiles.getAllProfiles();
208
197
  const now = Date.now();
209
- const oneMonthAgo = now - (30 * 24 * 60 * 60 * 1000);
210
-
198
+ const oneMonthAgo = now - 30 * 24 * 60 * 60 * 1000;
199
+
211
200
  // Only consider recent users for emerging trends
212
201
  const recentProfiles = profiles.filter(p => p.firstSeen && p.firstSeen > oneMonthAgo);
213
-
202
+
214
203
  if (recentProfiles.length < 3) {
215
204
  return { message: 'Not enough recent users to identify emerging clusters' };
216
205
  }
217
-
206
+
218
207
  const clusters = {};
219
-
208
+
220
209
  // Group by interests
221
210
  for (const profile of recentProfiles) {
222
211
  if (!profile.interests || profile.interests.length === 0) continue;
223
-
212
+
224
213
  for (const interest of profile.interests) {
225
214
  if (!clusters[interest]) {
226
215
  clusters[interest] = [];
@@ -228,7 +217,7 @@ async function identifyEmergingClusters() {
228
217
  clusters[interest].push(profile.handle);
229
218
  }
230
219
  }
231
-
220
+
232
221
  // Find clusters with 2+ recent users (potential communities)
233
222
  const emergingClusters = Object.entries(clusters)
234
223
  .filter(([interest, handles]) => handles.length >= 2)
@@ -239,7 +228,7 @@ async function identifyEmergingClusters() {
239
228
  potential: handles.length >= 3 ? 'high' : 'medium'
240
229
  }))
241
230
  .sort((a, b) => b.size - a.size);
242
-
231
+
243
232
  return emergingClusters;
244
233
  }
245
234
 
@@ -247,17 +236,17 @@ async function identifyEmergingClusters() {
247
236
  async function suggestOptimalTiming() {
248
237
  const profiles = await userProfiles.getAllProfiles();
249
238
  const now = Date.now();
250
-
239
+
251
240
  // Analyze when users are typically active
252
241
  const activityPatterns = {};
253
-
242
+
254
243
  for (const profile of profiles) {
255
244
  if (!profile.lastSeen) continue;
256
-
245
+
257
246
  const lastSeenDate = new Date(profile.lastSeen);
258
247
  const hour = lastSeenDate.getHours();
259
248
  const dayOfWeek = lastSeenDate.getDay();
260
-
249
+
261
250
  if (!activityPatterns[profile.handle]) {
262
251
  activityPatterns[profile.handle] = {
263
252
  preferredHours: [],
@@ -265,11 +254,11 @@ async function suggestOptimalTiming() {
265
254
  timezone: null // Could be inferred from activity patterns
266
255
  };
267
256
  }
268
-
257
+
269
258
  activityPatterns[profile.handle].preferredHours.push(hour);
270
259
  activityPatterns[profile.handle].preferredDays.push(dayOfWeek);
271
260
  }
272
-
261
+
273
262
  // Find common active hours across users
274
263
  const hourCounts = {};
275
264
  for (const pattern of Object.values(activityPatterns)) {
@@ -277,18 +266,16 @@ async function suggestOptimalTiming() {
277
266
  hourCounts[hour] = (hourCounts[hour] || 0) + 1;
278
267
  }
279
268
  }
280
-
269
+
281
270
  const peakHours = Object.entries(hourCounts)
282
- .sort(([,a], [,b]) => b - a)
271
+ .sort(([, a], [, b]) => b - a)
283
272
  .slice(0, 3)
284
273
  .map(([hour, count]) => ({ hour: parseInt(hour), userCount: count }));
285
-
274
+
286
275
  return {
287
276
  peakActivityHours: peakHours,
288
277
  totalActiveUsers: Object.keys(activityPatterns).length,
289
- recommendedConnectionTimes: peakHours.map(p =>
290
- `${p.hour}:00 (${p.userCount} users typically active)`
291
- )
278
+ recommendedConnectionTimes: peakHours.map(p => `${p.hour}:00 (${p.userCount} users typically active)`)
292
279
  };
293
280
  }
294
281
 
@@ -298,4 +285,4 @@ module.exports = {
298
285
  identifyEmergingClusters,
299
286
  suggestOptimalTiming,
300
287
  calculateSimpleMatchScore
301
- };
288
+ };
@@ -7,70 +7,34 @@
7
7
  * - Time formatting
8
8
  * - Display formatting
9
9
  * - Error handling
10
- * - Relevancy API fetching
11
10
  */
12
11
 
13
- const config = require('../../config');
14
-
15
- // ============ RELEVANCY API ============
16
-
17
- const RELEVANCY_API_TIMEOUT = 5000; // 5 seconds
18
-
19
12
  /**
20
- * Fetch relevant users from the Universal Relevancy API
21
- *
22
- * @param {string} handle - The requesting user's handle
23
- * @param {string} context - The context: 'discovery', 'dm_suggest', 'feed', 'notification'
24
- * @param {number} limit - Max results (default 5)
25
- * @returns {Promise<{ matches: object[], tier: string, fromApi: boolean } | null>}
13
+ * @typedef {Object} ToolResult
14
+ * @property {string} [display] - Formatted text to show the user
15
+ * @property {string} [hint] - Hint for Claude about next action
16
+ * @property {Object} [suggestion] - Suggested next action
17
+ * @property {number} [unread_count] - Unread message count
18
+ * @property {string} [for_handle] - Handle this result is relevant to
26
19
  */
27
- async function fetchRelevantUsers(handle, context = 'discovery', limit = 5) {
28
- try {
29
- const apiUrl = config.getApiUrl();
30
- const controller = new AbortController();
31
- const timeout = setTimeout(() => controller.abort(), RELEVANCY_API_TIMEOUT);
32
-
33
- const url = `${apiUrl}/api/relevancy?handle=${encodeURIComponent(handle)}&context=${context}&limit=${limit}`;
34
-
35
- const response = await fetch(url, {
36
- method: 'GET',
37
- headers: {
38
- 'Accept': 'application/json',
39
- 'User-Agent': 'vibe-mcp-client'
40
- },
41
- signal: controller.signal
42
- });
43
-
44
- clearTimeout(timeout);
45
-
46
- if (!response.ok) {
47
- console.log(`[relevancy] API returned ${response.status}`);
48
- return null;
49
- }
50
20
 
51
- const data = await response.json();
21
+ /**
22
+ * @typedef {Object} ToolDefinition
23
+ * @property {string} name - Tool name (vibe_*)
24
+ * @property {string} description - Human-readable description
25
+ * @property {Object} inputSchema - JSON Schema for tool arguments
26
+ * @property {string} inputSchema.type - Always "object"
27
+ * @property {Object} inputSchema.properties - Argument definitions
28
+ * @property {string[]} [inputSchema.required] - Required argument names
29
+ */
52
30
 
53
- if (!data.success || !data.matches) {
54
- console.log('[relevancy] API returned unsuccessful response');
55
- return null;
56
- }
31
+ /**
32
+ * @typedef {Object} ToolModule
33
+ * @property {ToolDefinition} definition - Tool schema definition
34
+ * @property {function(Object): Promise<ToolResult>} handler - Tool handler function
35
+ */
57
36
 
58
- return {
59
- matches: data.matches,
60
- tier: data.tier,
61
- socialStats: data.socialStats,
62
- fromApi: true
63
- };
64
- } catch (e) {
65
- // Log but don't fail - caller should handle null gracefully
66
- if (e.name === 'AbortError') {
67
- console.log('[relevancy] API timeout');
68
- } else {
69
- console.log('[relevancy] API error:', e.message);
70
- }
71
- return null;
72
- }
73
- }
37
+ const config = require('../../config');
74
38
 
75
39
  // ============ INIT CHECK ============
76
40
 
@@ -93,7 +57,7 @@ function requireInit() {
93
57
  * @returns {Function} - Wrapped handler
94
58
  */
95
59
  function withInit(handler) {
96
- return async function(args) {
60
+ return async function (args) {
97
61
  const initCheck = requireInit();
98
62
  if (initCheck) return initCheck;
99
63
  return handler(args);
@@ -245,7 +209,7 @@ function error(message) {
245
209
  * @returns {Function} - Wrapped handler with try/catch
246
210
  */
247
211
  function withErrorHandling(handler) {
248
- return async function(args) {
212
+ return async function (args) {
249
213
  try {
250
214
  return await handler(args);
251
215
  } catch (e) {
@@ -262,7 +226,7 @@ function withErrorHandling(handler) {
262
226
  * @returns {Function} - Combined wrapper
263
227
  */
264
228
  function compose(...wrappers) {
265
- return function(handler) {
229
+ return function (handler) {
266
230
  return wrappers.reduceRight((h, wrapper) => wrapper(h), handler);
267
231
  };
268
232
  }
@@ -291,10 +255,20 @@ function validateRequired(args, required) {
291
255
  return null;
292
256
  }
293
257
 
294
- module.exports = {
295
- // Relevancy
296
- fetchRelevantUsers,
258
+ // ============ DEBUG ============
259
+
260
+ /**
261
+ * Debug logging — only outputs when VIBE_DEBUG=true
262
+ * @param {string} tag - Log tag (e.g., 'DM', 'GAME')
263
+ * @param {...any} args - Values to log
264
+ */
265
+ function debug(tag, ...args) {
266
+ if (process.env.VIBE_DEBUG === 'true') {
267
+ console.error(`[vibe:${tag}]`, ...args);
268
+ }
269
+ }
297
270
 
271
+ module.exports = {
298
272
  // Init
299
273
  requireInit,
300
274
  withInit,
@@ -322,5 +296,8 @@ module.exports = {
322
296
  withDefaults,
323
297
 
324
298
  // Validation
325
- validateRequired
299
+ validateRequired,
300
+
301
+ // Debug
302
+ debug
326
303
  };