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.
- package/LICENSE +21 -0
- package/README.md +280 -47
- package/auto-update.js +10 -15
- package/config.js +36 -31
- package/crypto.js +1 -6
- package/debug.js +12 -0
- package/discord.js +19 -19
- package/eslint.config.js +54 -0
- package/index.js +217 -207
- package/intelligence/index.js +2 -9
- package/intelligence/infer.js +10 -16
- package/intelligence/patterns.js +23 -18
- package/intelligence/proactive.js +16 -15
- package/intelligence/serendipity.js +57 -20
- package/memory.js +13 -8
- package/migrate-v2.js +72 -0
- package/notification-emitter.js +2 -2
- package/notify.js +39 -14
- package/package.json +28 -29
- package/post-install.js +141 -0
- package/presence.js +2 -2
- package/prompts.js +5 -9
- package/protocol/index.js +123 -87
- package/protocol/telegram-commands.js +36 -37
- package/store/api.js +358 -529
- package/store/local.js +9 -10
- package/store/profiles.js +48 -192
- package/store/reservations.js +2 -9
- package/store/skills.js +69 -71
- package/store/sqlite.js +355 -0
- package/test-skills-bootstrap.js +20 -0
- package/test-v2-integration.js +385 -0
- package/tools/_actions.js +48 -387
- package/tools/_connection-queue.js +45 -56
- package/tools/_discovery-enhanced.js +52 -57
- package/tools/_discovery.js +87 -185
- package/tools/{l2-status.js → _experimental/l2-status.js} +68 -70
- package/tools/{shipback.js → _experimental/shipback.js} +4 -3
- package/tools/_proactive-discovery.js +60 -73
- package/tools/_shared/index.js +41 -64
- package/tools/admin-inbox.js +10 -15
- package/tools/agents.js +1 -1
- package/tools/artifact-create.js +13 -23
- package/tools/artifact-view.js +4 -4
- package/tools/{_deprecated/back.js → back.js} +1 -1
- package/tools/bye.js +3 -5
- package/tools/consent.js +2 -2
- package/tools/context.js +9 -10
- package/tools/crossword.js +3 -2
- package/tools/discover.js +94 -356
- package/tools/dm.js +27 -86
- package/tools/doctor.js +12 -41
- package/tools/drawing.js +34 -20
- package/tools/echo.js +11 -11
- package/tools/feed.js +30 -58
- package/tools/follow.js +64 -187
- package/tools/{_deprecated/forget.js → forget.js} +4 -7
- package/tools/game.js +144 -48
- package/tools/handoff.js +6 -8
- package/tools/help.js +3 -3
- package/tools/idea.js +15 -27
- package/tools/inbox.js +121 -293
- package/tools/init.js +54 -151
- package/tools/invite.js +8 -21
- package/tools/migrate.js +27 -24
- package/tools/multiplayer-game.js +50 -40
- package/tools/{_deprecated/mute.js → mute.js} +4 -3
- package/tools/notifications.js +58 -48
- package/tools/observe.js +12 -15
- package/tools/onboarding.js +8 -11
- package/tools/open.js +13 -144
- package/tools/party-game.js +23 -12
- package/tools/patterns.js +2 -1
- package/tools/ping.js +5 -7
- package/tools/react.js +28 -30
- package/tools/{_deprecated/recall.js → recall.js} +5 -10
- package/tools/release.js +4 -2
- package/tools/{_deprecated/remember.js → remember.js} +4 -6
- package/tools/report.js +2 -2
- package/tools/request.js +6 -26
- package/tools/reserve.js +1 -1
- package/tools/session-fork.js +97 -0
- package/tools/session-save.js +109 -0
- package/tools/settings.js +30 -99
- package/tools/ship.js +74 -56
- package/tools/{_deprecated/skills-exchange.js → skills-exchange.js} +38 -39
- package/tools/social-inbox.js +22 -28
- package/tools/social-post.js +24 -27
- package/tools/solo-game.js +54 -46
- package/tools/start.js +14 -148
- package/tools/status.js +21 -68
- package/tools/submit.js +4 -2
- package/tools/suggest-tags.js +36 -33
- package/tools/summarize.js +19 -16
- package/tools/tag-suggestions.js +72 -73
- package/tools/test.js +1 -1
- package/tools/{_deprecated/tictactoe.js → tictactoe.js} +26 -26
- package/tools/token.js +4 -4
- package/tools/update.js +1 -2
- package/tools/watch.js +132 -112
- package/tools/who.js +20 -40
- package/tools/{_deprecated/wordassociation.js → wordassociation.js} +23 -20
- package/tools/workshop-buddy.js +52 -53
- package/tools/x-mentions.js +0 -1
- package/tools/x-reply.js +0 -1
- package/twitter.js +14 -20
- package/version.json +8 -10
- package/webhook-runner.js +132 -0
- package/auth-store.js +0 -148
- package/bridges/bridge-monitor.js +0 -388
- package/bridges/discord-bot.js +0 -431
- package/bridges/farcaster.js +0 -299
- package/bridges/telegram.js +0 -261
- package/bridges/webhook-health.js +0 -420
- package/bridges/webhook-server.js +0 -437
- package/bridges/whatsapp.js +0 -441
- package/bridges/x-webhook.js +0 -423
- package/games/arcade.js +0 -406
- package/games/chess.js +0 -451
- package/games/colorguess.js +0 -343
- package/games/crossword-words.js +0 -171
- package/games/crossword.js +0 -461
- package/games/drawing.js +0 -347
- package/games/gameroulette.js +0 -300
- package/games/gamerouter.js +0 -336
- package/games/gamestatus.js +0 -337
- package/games/guessnumber.js +0 -209
- package/games/hangman.js +0 -279
- package/games/memory.js +0 -338
- package/games/multiplayer-tictactoe.js +0 -389
- package/games/pixelart.js +0 -399
- package/games/quickduel.js +0 -354
- package/games/riddle.js +0 -371
- package/games/rockpaperscissors.js +0 -291
- package/games/snake.js +0 -406
- package/games/storybuilder.js +0 -343
- package/games/tictactoe.js +0 -345
- package/games/twentyquestions.js +0 -286
- package/games/twotruths.js +0 -207
- package/games/werewolf.js +0 -508
- package/games/wordassociation.js +0 -247
- package/games/wordchain.js +0 -135
- package/intelligence/interests.js +0 -369
- package/setup.js +0 -480
- package/smart-inbox.js +0 -276
- package/tools/_deprecated/auto-suggest-connections.js +0 -304
- package/tools/_deprecated/bootstrap-skills.js +0 -231
- package/tools/_deprecated/bridge-dashboard.js +0 -342
- package/tools/_deprecated/bridge-health.js +0 -400
- package/tools/_deprecated/bridge-live.js +0 -384
- package/tools/_deprecated/bridges.js +0 -383
- package/tools/_deprecated/colorguess.js +0 -281
- package/tools/_deprecated/discover-insights.js +0 -379
- package/tools/_deprecated/discover-momentum.js +0 -256
- package/tools/_deprecated/discovery-analytics.js +0 -345
- package/tools/_deprecated/discovery-auto-suggest.js +0 -275
- package/tools/_deprecated/discovery-bootstrap.js +0 -267
- package/tools/_deprecated/discovery-daily.js +0 -375
- package/tools/_deprecated/discovery-dashboard.js +0 -385
- package/tools/_deprecated/discovery-digest.js +0 -314
- package/tools/_deprecated/discovery-hub.js +0 -357
- package/tools/_deprecated/discovery-insights.js +0 -384
- package/tools/_deprecated/discovery-momentum.js +0 -281
- package/tools/_deprecated/discovery-monitor.js +0 -319
- package/tools/_deprecated/discovery-proactive.js +0 -300
- package/tools/_deprecated/draw.js +0 -317
- package/tools/_deprecated/farcaster.js +0 -307
- package/tools/_deprecated/games-catalog.js +0 -376
- package/tools/_deprecated/games.js +0 -313
- package/tools/_deprecated/guessnumber.js +0 -194
- package/tools/_deprecated/hangman.js +0 -129
- package/tools/_deprecated/multiplayer-tictactoe.js +0 -303
- package/tools/_deprecated/riddle.js +0 -240
- package/tools/_deprecated/run-bootstrap.js +0 -69
- package/tools/_deprecated/skills-analytics.js +0 -349
- package/tools/_deprecated/skills-bootstrap.js +0 -301
- package/tools/_deprecated/skills-dashboard.js +0 -268
- package/tools/_deprecated/skills.js +0 -380
- package/tools/_deprecated/smart-intro.js +0 -353
- package/tools/_deprecated/storybuilder.js +0 -331
- package/tools/_deprecated/telegram-bot.js +0 -183
- package/tools/_deprecated/telegram-setup.js +0 -214
- package/tools/_deprecated/twentyquestions.js +0 -143
- package/tools/_shared.js +0 -234
- package/tools/_work-context.js +0 -338
- package/tools/_work-context.manual-test.js +0 -199
- package/tools/_work-context.test.js +0 -260
- package/tools/activity.js +0 -220
- package/tools/agent-treasury.js +0 -288
- package/tools/analytics.js +0 -191
- package/tools/approve.js +0 -197
- package/tools/arcade.js +0 -173
- package/tools/artifacts-price.js +0 -107
- package/tools/ask-expert.js +0 -160
- package/tools/available.js +0 -120
- package/tools/become-expert.js +0 -150
- package/tools/broadcast.js +0 -325
- package/tools/chat.js +0 -202
- package/tools/collaborative-drawing.js +0 -286
- package/tools/connection-status.js +0 -178
- package/tools/earnings.js +0 -126
- package/tools/friends.js +0 -207
- package/tools/genesis.js +0 -233
- package/tools/gig-browse.js +0 -206
- package/tools/gig-complete.js +0 -144
- package/tools/health.js +0 -87
- package/tools/leaderboard.js +0 -117
- package/tools/lib/git-apply.js +0 -206
- package/tools/lib/git-bundle.js +0 -407
- package/tools/mint.js +0 -377
- package/tools/plan.js +0 -225
- package/tools/profile.js +0 -219
- package/tools/proof-of-work.js +0 -144
- package/tools/pulse.js +0 -218
- package/tools/reply.js +0 -166
- package/tools/reputation.js +0 -175
- package/tools/schedule.js +0 -367
- package/tools/search-messages.js +0 -123
- package/tools/session.js +0 -467
- package/tools/session_price.js +0 -128
- package/tools/smart-check.js +0 -201
- package/tools/social-processor.js +0 -445
- package/tools/streak.js +0 -147
- package/tools/stuck.js +0 -297
- package/tools/subscribe.js +0 -148
- package/tools/subscriptions.js +0 -134
- package/tools/tip.js +0 -193
- package/tools/wallet.js +0 -269
- package/tools/webhook-test.js +0 -388
- package/tools/withdraw.js +0 -145
- package/tools/work-summary.js +0 -96
- package/tools/workshop.js +0 -327
- /package/tools/{l2-bridge.js → _experimental/l2-bridge.js} +0 -0
- /package/tools/{l2.js → _experimental/l2.js} +0 -0
- /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
|
|
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
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
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() -
|
|
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
|
-
|
|
432
|
-
syncFromPresence,
|
|
433
|
-
inferMissingInterests,
|
|
434
|
-
getProfilesWithInterests
|
|
435
|
-
};
|
|
290
|
+
cleanupOldProfiles
|
|
291
|
+
};
|
package/store/reservations.js
CHANGED
|
@@ -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;
|