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
package/store/local.js CHANGED
@@ -91,7 +91,9 @@ function loadMessages() {
91
91
  try {
92
92
  if (fs.existsSync(MESSAGES_FILE)) {
93
93
  const content = fs.readFileSync(MESSAGES_FILE, 'utf8');
94
- return content.trim().split('\n')
94
+ return content
95
+ .trim()
96
+ .split('\n')
95
97
  .filter(line => line.length > 0)
96
98
  .map(line => JSON.parse(line));
97
99
  }
@@ -122,9 +124,7 @@ async function getInbox(handle) {
122
124
  const h = handle.toLowerCase().replace('@', '');
123
125
 
124
126
  // Get messages TO this user
125
- return messages
126
- .filter(m => m.to === h)
127
- .sort((a, b) => b.timestamp - a.timestamp);
127
+ return messages.filter(m => m.to === h).sort((a, b) => b.timestamp - a.timestamp);
128
128
  }
129
129
 
130
130
  async function getUnreadCount(handle) {
@@ -144,10 +144,7 @@ async function getThread(myHandle, theirHandle) {
144
144
 
145
145
  // Get messages between these two users
146
146
  return messages
147
- .filter(m =>
148
- (m.from === me && m.to === them) ||
149
- (m.from === them && m.to === me)
150
- )
147
+ .filter(m => (m.from === me && m.to === them) || (m.from === them && m.to === me))
151
148
  .sort((a, b) => a.timestamp - b.timestamp);
152
149
  }
153
150
 
@@ -175,7 +172,9 @@ function loadSkillExchanges() {
175
172
  try {
176
173
  if (fs.existsSync(SKILL_EXCHANGE_FILE)) {
177
174
  const content = fs.readFileSync(SKILL_EXCHANGE_FILE, 'utf8');
178
- return content.trim().split('\n')
175
+ return content
176
+ .trim()
177
+ .split('\n')
179
178
  .filter(line => line.length > 0)
180
179
  .map(line => JSON.parse(line));
181
180
  }
@@ -260,4 +259,4 @@ module.exports = {
260
259
 
261
260
  // Helpers
262
261
  formatTimeAgo
263
- };
262
+ };
package/store/profiles.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Stores rich user data for matchmaking:
5
5
  * - What they're building
6
- * - Interests (broad topics they care about)
6
+ * - Interests (broad topics they care about)
7
7
  * - Tags (specific skills/technologies)
8
8
  * - Activity patterns
9
9
  * - Connection history
@@ -41,17 +41,19 @@ function saveProfiles(profiles) {
41
41
  async function getProfile(handle) {
42
42
  const profiles = loadProfiles();
43
43
  const key = handle.toLowerCase().replace('@', '');
44
-
45
- return profiles[key] || {
46
- handle: key,
47
- building: null,
48
- interests: [],
49
- tags: [],
50
- lastSeen: null,
51
- firstSeen: null,
52
- connections: [],
53
- ships: []
54
- };
44
+
45
+ return (
46
+ profiles[key] || {
47
+ handle: key,
48
+ building: null,
49
+ interests: [],
50
+ tags: [],
51
+ lastSeen: null,
52
+ firstSeen: null,
53
+ connections: [],
54
+ ships: []
55
+ }
56
+ );
55
57
  }
56
58
 
57
59
  // Update user profile
@@ -77,17 +79,19 @@ async function updateProfile(handle, updates) {
77
79
  if (updates.interests) {
78
80
  updated.interests = Array.isArray(updates.interests)
79
81
  ? updates.interests
80
- : updates.interests.split(',').map(s => s.trim()).filter(s => s);
81
-
82
- // IMPORTANT: Clear inferred flag when user explicitly sets interests
83
- // This prevents inferMissingInterests() from overwriting explicit choices
84
- updated.interests_inferred = false;
82
+ : updates.interests
83
+ .split(',')
84
+ .map(s => s.trim())
85
+ .filter(s => s);
85
86
  }
86
87
 
87
88
  if (updates.tags) {
88
89
  updated.tags = Array.isArray(updates.tags)
89
90
  ? updates.tags
90
- : updates.tags.split(',').map(s => s.trim()).filter(s => s);
91
+ : updates.tags
92
+ .split(',')
93
+ .map(s => s.trim())
94
+ .filter(s => s);
91
95
  }
92
96
 
93
97
  // Update timestamps
@@ -113,23 +117,23 @@ async function recordConnection(from, to, reason) {
113
117
  const profiles = loadProfiles();
114
118
  const fromKey = from.toLowerCase().replace('@', '');
115
119
  const toKey = to.toLowerCase().replace('@', '');
116
-
120
+
117
121
  const connection = {
118
122
  timestamp: Date.now(),
119
123
  reason,
120
124
  suggested_by: 'discovery-agent'
121
125
  };
122
-
126
+
123
127
  // Add to both profiles
124
128
  if (!profiles[fromKey]) profiles[fromKey] = createEmptyProfile(fromKey);
125
129
  if (!profiles[toKey]) profiles[toKey] = createEmptyProfile(toKey);
126
-
130
+
127
131
  if (!profiles[fromKey].connections) profiles[fromKey].connections = [];
128
132
  if (!profiles[toKey].connections) profiles[toKey].connections = [];
129
-
133
+
130
134
  profiles[fromKey].connections.push({ handle: toKey, ...connection });
131
135
  profiles[toKey].connections.push({ handle: fromKey, ...connection });
132
-
136
+
133
137
  saveProfiles(profiles);
134
138
  }
135
139
 
@@ -137,23 +141,23 @@ async function recordConnection(from, to, reason) {
137
141
  async function recordShip(handle, what) {
138
142
  const profiles = loadProfiles();
139
143
  const key = handle.toLowerCase().replace('@', '');
140
-
144
+
141
145
  if (!profiles[key]) {
142
146
  profiles[key] = createEmptyProfile(key);
143
147
  }
144
-
148
+
145
149
  if (!profiles[key].ships) {
146
150
  profiles[key].ships = [];
147
151
  }
148
-
152
+
149
153
  profiles[key].ships.unshift({
150
154
  what,
151
155
  timestamp: Date.now()
152
156
  });
153
-
157
+
154
158
  // Keep only last 10 ships
155
159
  profiles[key].ships = profiles[key].ships.slice(0, 10);
156
-
160
+
157
161
  saveProfiles(profiles);
158
162
  }
159
163
 
@@ -161,7 +165,7 @@ async function recordShip(handle, what) {
161
165
  async function updateLastSeen(handle) {
162
166
  const profiles = loadProfiles();
163
167
  const key = handle.toLowerCase().replace('@', '');
164
-
168
+
165
169
  if (profiles[key]) {
166
170
  profiles[key].lastSeen = Date.now();
167
171
  saveProfiles(profiles);
@@ -172,7 +176,7 @@ async function updateLastSeen(handle) {
172
176
  async function getConnectionHistory(handle1, handle2) {
173
177
  const profile = await getProfile(handle1);
174
178
  const key2 = handle2.toLowerCase().replace('@', '');
175
-
179
+
176
180
  return profile.connections?.filter(c => c.handle === key2) || [];
177
181
  }
178
182
 
@@ -186,27 +190,23 @@ async function hasBeenConnected(handle1, handle2) {
186
190
  async function getUsersByInterest(interest) {
187
191
  const profiles = await getAllProfiles();
188
192
  const searchTerm = interest.toLowerCase();
189
-
190
- return profiles.filter(p =>
191
- p.interests?.some(i => i.toLowerCase().includes(searchTerm))
192
- );
193
+
194
+ return profiles.filter(p => p.interests?.some(i => i.toLowerCase().includes(searchTerm)));
193
195
  }
194
196
 
195
197
  // Get users by tag
196
198
  async function getUsersByTag(tag) {
197
199
  const profiles = await getAllProfiles();
198
200
  const searchTerm = tag.toLowerCase();
199
-
200
- return profiles.filter(p =>
201
- p.tags?.some(t => t.toLowerCase().includes(searchTerm))
202
- );
201
+
202
+ return profiles.filter(p => p.tags?.some(t => t.toLowerCase().includes(searchTerm)));
203
203
  }
204
204
 
205
205
  // Get trending interests
206
206
  async function getTrendingInterests() {
207
207
  const profiles = await getAllProfiles();
208
208
  const interestCount = {};
209
-
209
+
210
210
  for (const profile of profiles) {
211
211
  if (profile.interests) {
212
212
  for (const interest of profile.interests) {
@@ -214,18 +214,18 @@ async function getTrendingInterests() {
214
214
  }
215
215
  }
216
216
  }
217
-
217
+
218
218
  return Object.entries(interestCount)
219
- .sort(([,a], [,b]) => b - a)
219
+ .sort(([, a], [, b]) => b - a)
220
220
  .slice(0, 10)
221
221
  .map(([interest, count]) => ({ interest, count }));
222
222
  }
223
223
 
224
- // Get trending tags
224
+ // Get trending tags
225
225
  async function getTrendingTags() {
226
226
  const profiles = await getAllProfiles();
227
227
  const tagCount = {};
228
-
228
+
229
229
  for (const profile of profiles) {
230
230
  if (profile.tags) {
231
231
  for (const tag of profile.tags) {
@@ -233,9 +233,9 @@ async function getTrendingTags() {
233
233
  }
234
234
  }
235
235
  }
236
-
236
+
237
237
  return Object.entries(tagCount)
238
- .sort(([,a], [,b]) => b - a)
238
+ .sort(([, a], [, b]) => b - a)
239
239
  .slice(0, 15)
240
240
  .map(([tag, count]) => ({ tag, count }));
241
241
  }
@@ -257,7 +257,7 @@ function createEmptyProfile(handle) {
257
257
  // Clean up old profiles (for maintenance)
258
258
  async function cleanupOldProfiles(daysThreshold = 30) {
259
259
  const profiles = loadProfiles();
260
- const cutoff = Date.now() - (daysThreshold * 24 * 60 * 60 * 1000);
260
+ const cutoff = Date.now() - daysThreshold * 24 * 60 * 60 * 1000;
261
261
 
262
262
  let cleaned = 0;
263
263
  for (const [key, profile] of Object.entries(profiles)) {
@@ -274,146 +274,6 @@ async function cleanupOldProfiles(daysThreshold = 30) {
274
274
  return cleaned;
275
275
  }
276
276
 
277
- /**
278
- * Sync profile data from API presence
279
- * Called after fetching active users to keep local profiles up-to-date
280
- *
281
- * @param {Array} presenceData - Array of user objects from /api/presence
282
- * @returns {number} Number of profiles synced
283
- */
284
- async function syncFromPresence(presenceData) {
285
- if (!presenceData || !Array.isArray(presenceData)) return 0;
286
-
287
- const profiles = loadProfiles();
288
- let changed = false;
289
- let buildingsUpdated = 0;
290
-
291
- for (const user of presenceData) {
292
- // Skip users without a handle
293
- if (!user.handle && !user.username) continue;
294
-
295
- const handle = user.handle || user.username;
296
- const key = handle.toLowerCase().replace('@', '');
297
-
298
- // Create profile if it doesn't exist
299
- const isNew = !profiles[key];
300
- if (isNew) {
301
- profiles[key] = createEmptyProfile(key);
302
- changed = true;
303
- }
304
-
305
- // Sync building description (one_liner from presence)
306
- const building = user.one_liner || user.workingOn;
307
- if (building && building !== profiles[key].building) {
308
- profiles[key].building = building;
309
- buildingsUpdated++;
310
- changed = true;
311
- }
312
-
313
- // Update timestamps (always sync these)
314
- if (user.lastSeen) {
315
- const ts = typeof user.lastSeen === 'number'
316
- ? user.lastSeen
317
- : new Date(user.lastSeen).getTime();
318
- // Only update if valid and different
319
- if (!isNaN(ts) && ts !== profiles[key].lastSeen) {
320
- profiles[key].lastSeen = ts;
321
- changed = true;
322
- }
323
- }
324
- if (user.firstSeen && !profiles[key].firstSeen) {
325
- const ts = typeof user.firstSeen === 'number'
326
- ? user.firstSeen
327
- : new Date(user.firstSeen).getTime();
328
- if (!isNaN(ts)) {
329
- profiles[key].firstSeen = ts;
330
- changed = true;
331
- }
332
- }
333
-
334
- // Sync mood if present and different
335
- if (user.mood && user.mood !== profiles[key].mood) {
336
- profiles[key].mood = user.mood;
337
- changed = true;
338
- }
339
- }
340
-
341
- // Only write if something actually changed
342
- if (changed) {
343
- saveProfiles(profiles);
344
- if (buildingsUpdated > 0) {
345
- console.error(`[profiles] Synced ${buildingsUpdated} building descriptions from presence`);
346
- }
347
- }
348
-
349
- return buildingsUpdated;
350
- }
351
-
352
- /**
353
- * Infer interests from building descriptions for profiles that don't have them
354
- * Uses suggestInterestsFromBuilding() from _discovery.js
355
- *
356
- * @returns {number} Number of profiles with newly inferred interests
357
- */
358
- async function inferMissingInterests() {
359
- // Import discovery module (lazy load to avoid circular deps)
360
- let suggestInterestsFromBuilding;
361
- try {
362
- const discovery = require('../tools/_discovery');
363
- suggestInterestsFromBuilding = discovery.suggestInterestsFromBuilding;
364
- } catch (e) {
365
- console.error('[profiles] Failed to load discovery module:', e.message);
366
- return 0;
367
- }
368
-
369
- const profiles = loadProfiles();
370
- let inferred = 0;
371
- let changed = false;
372
-
373
- for (const [key, profile] of Object.entries(profiles)) {
374
- // Skip if user has explicit (non-inferred) interests
375
- if (profile.interests?.length > 0 && !profile.interests_inferred) {
376
- continue;
377
- }
378
-
379
- // Infer from building description
380
- if (profile.building) {
381
- const suggested = suggestInterestsFromBuilding(profile.building);
382
- if (suggested.length > 0) {
383
- // Only update if interests actually changed (avoid repeated writes)
384
- const existingInterests = (profile.interests || []).slice().sort().join(',');
385
- const newInterests = suggested.slice().sort().join(',');
386
-
387
- if (existingInterests !== newInterests) {
388
- profiles[key].interests = suggested;
389
- profiles[key].interests_inferred = true; // Mark as auto-generated
390
- profiles[key].interests_updated_at = Date.now();
391
- inferred++;
392
- changed = true;
393
- }
394
- }
395
- }
396
- }
397
-
398
- if (changed) {
399
- saveProfiles(profiles);
400
- console.error(`[profiles] Inferred interests for ${inferred} profiles`);
401
- }
402
-
403
- return inferred;
404
- }
405
-
406
- /**
407
- * Get profiles with inferred interests for discovery
408
- * Combines explicit and inferred interests
409
- *
410
- * @returns {Array} All profiles with interests (explicit or inferred)
411
- */
412
- async function getProfilesWithInterests() {
413
- const profiles = await getAllProfiles();
414
- return profiles.filter(p => p.interests?.length > 0);
415
- }
416
-
417
277
  module.exports = {
418
278
  getProfile,
419
279
  updateProfile,
@@ -427,9 +287,5 @@ module.exports = {
427
287
  getUsersByTag,
428
288
  getTrendingInterests,
429
289
  getTrendingTags,
430
- cleanupOldProfiles,
431
- // Presence sync (Phase 1)
432
- syncFromPresence,
433
- inferMissingInterests,
434
- getProfilesWithInterests
435
- };
290
+ cleanupOldProfiles
291
+ };
@@ -186,12 +186,7 @@ function checkConflicts(paths, exclusive = true, scope = null) {
186
186
 
187
187
  // Create a new reservation
188
188
  function create(owner, paths, options = {}) {
189
- const {
190
- ttl_seconds = 3600,
191
- exclusive = true,
192
- reason = null,
193
- thread_id = null
194
- } = options;
189
+ const { ttl_seconds = 3600, exclusive = true, reason = null, thread_id = null } = options;
195
190
 
196
191
  const scope = getScope();
197
192
  const now = new Date();
@@ -273,9 +268,7 @@ function list(options = {}) {
273
268
 
274
269
  // Filter by path
275
270
  if (path_filter) {
276
- reservations = reservations.filter(r =>
277
- r.paths.some(p => p.includes(path_filter) || path_filter.includes(p))
278
- );
271
+ reservations = reservations.filter(r => r.paths.some(p => p.includes(path_filter) || path_filter.includes(p)));
279
272
  }
280
273
 
281
274
  return reservations;