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.
- package/README.md +47 -252
- package/analytics.js +107 -0
- package/auth-store.js +148 -0
- package/auto-update.js +130 -0
- package/bridges/bridge-monitor.js +388 -0
- package/bridges/discord-bot.js +431 -0
- package/bridges/farcaster.js +299 -0
- package/bridges/telegram.js +261 -0
- package/bridges/webhook-health.js +420 -0
- package/bridges/webhook-server.js +437 -0
- package/bridges/whatsapp.js +441 -0
- package/bridges/x-webhook.js +423 -0
- package/config.js +27 -15
- package/games/arcade.js +406 -0
- package/games/chess.js +451 -0
- package/games/colorguess.js +343 -0
- package/games/crossword-words.js +171 -0
- package/games/crossword.js +461 -0
- package/games/drawing.js +347 -0
- package/games/gameroulette.js +300 -0
- package/games/gamerouter.js +336 -0
- package/games/gamestatus.js +337 -0
- package/games/guessnumber.js +209 -0
- package/games/hangman.js +279 -0
- package/games/memory.js +338 -0
- package/games/multiplayer-tictactoe.js +389 -0
- package/games/pixelart.js +399 -0
- package/games/quickduel.js +354 -0
- package/games/riddle.js +371 -0
- package/games/rockpaperscissors.js +291 -0
- package/games/snake.js +406 -0
- package/games/storybuilder.js +343 -0
- package/games/tictactoe.js +345 -0
- package/games/twentyquestions.js +286 -0
- package/games/twotruths.js +207 -0
- package/games/werewolf.js +508 -0
- package/games/wordassociation.js +247 -0
- package/games/wordchain.js +135 -0
- package/index.js +116 -159
- package/intelligence/index.js +9 -2
- package/intelligence/interests.js +369 -0
- package/notification-emitter.js +77 -0
- package/notify.js +5 -1
- package/package.json +21 -16
- package/prompts.js +1 -1
- package/protocol/index.js +73 -0
- package/setup.js +480 -0
- package/smart-inbox.js +276 -0
- package/store/api.js +536 -215
- package/store/profiles.js +160 -12
- package/tools/_actions.js +362 -21
- package/tools/_discovery.js +119 -26
- package/tools/_shared/index.js +64 -0
- package/tools/_shared.js +234 -0
- package/tools/_work-context.js +338 -0
- package/tools/_work-context.manual-test.js +199 -0
- package/tools/_work-context.test.js +260 -0
- package/tools/activity.js +220 -0
- package/tools/analytics.js +191 -0
- package/tools/approve.js +197 -0
- package/tools/artifact-create.js +14 -3
- package/tools/artifacts-price.js +107 -0
- package/tools/available.js +120 -0
- package/tools/broadcast.js +325 -0
- package/tools/chat.js +202 -0
- package/tools/collaborative-drawing.js +1 -1
- package/tools/connection-status.js +178 -0
- package/tools/discover.js +350 -34
- package/tools/dm.js +80 -8
- package/tools/earnings.js +126 -0
- package/tools/feed.js +35 -4
- package/tools/follow.js +224 -0
- package/tools/friends.js +207 -0
- package/tools/gig-browse.js +206 -0
- package/tools/gig-complete.js +144 -0
- package/tools/health.js +87 -0
- package/tools/help.js +3 -3
- package/tools/idea.js +9 -2
- package/tools/inbox.js +289 -105
- package/tools/init.js +131 -34
- package/tools/invite.js +15 -4
- package/tools/leaderboard.js +117 -0
- package/tools/lib/git-apply.js +206 -0
- package/tools/lib/git-bundle.js +407 -0
- package/tools/migrate.js +3 -3
- package/tools/multiplayer-game.js +1 -1
- package/tools/onboarding.js +7 -7
- package/tools/open.js +143 -12
- package/tools/party-game.js +1 -1
- package/tools/plan.js +225 -0
- package/tools/proof-of-work.js +144 -0
- package/tools/reply.js +166 -0
- package/tools/report.js +1 -1
- package/tools/request.js +17 -3
- package/tools/schedule.js +367 -0
- package/tools/search-messages.js +123 -0
- package/tools/session.js +467 -0
- package/tools/session_price.js +128 -0
- package/tools/settings.js +90 -2
- package/tools/ship.js +30 -7
- package/tools/smart-check.js +201 -0
- package/tools/start.js +147 -12
- package/tools/status.js +53 -6
- package/tools/streak.js +147 -0
- package/tools/stuck.js +297 -0
- package/tools/subscribe.js +148 -0
- package/tools/subscriptions.js +134 -0
- package/tools/suggest-tags.js +6 -8
- package/tools/tag-suggestions.js +1 -1
- package/tools/tip.js +150 -77
- package/tools/token.js +4 -4
- package/tools/update.js +1 -1
- package/tools/wallet.js +221 -79
- package/tools/watch.js +157 -0
- package/tools/who.js +30 -1
- package/tools/withdraw.js +145 -0
- package/tools/work-summary.js +96 -0
- package/version.json +10 -8
- package/LICENSE +0 -21
- package/store/sqlite.js +0 -347
- /package/tools/{auto-suggest-connections.js → _deprecated/auto-suggest-connections.js} +0 -0
- /package/tools/{away.js → _deprecated/away.js} +0 -0
- /package/tools/{back.js → _deprecated/back.js} +0 -0
- /package/tools/{bootstrap-skills.js → _deprecated/bootstrap-skills.js} +0 -0
- /package/tools/{bridge-dashboard.js → _deprecated/bridge-dashboard.js} +0 -0
- /package/tools/{bridge-health.js → _deprecated/bridge-health.js} +0 -0
- /package/tools/{bridge-live.js → _deprecated/bridge-live.js} +0 -0
- /package/tools/{bridges.js → _deprecated/bridges.js} +0 -0
- /package/tools/{colorguess.js → _deprecated/colorguess.js} +0 -0
- /package/tools/{discover-insights.js → _deprecated/discover-insights.js} +0 -0
- /package/tools/{discover-momentum.js → _deprecated/discover-momentum.js} +0 -0
- /package/tools/{discovery-analytics.js → _deprecated/discovery-analytics.js} +0 -0
- /package/tools/{discovery-auto-suggest.js → _deprecated/discovery-auto-suggest.js} +0 -0
- /package/tools/{discovery-bootstrap.js → _deprecated/discovery-bootstrap.js} +0 -0
- /package/tools/{discovery-daily.js → _deprecated/discovery-daily.js} +0 -0
- /package/tools/{discovery-dashboard.js → _deprecated/discovery-dashboard.js} +0 -0
- /package/tools/{discovery-digest.js → _deprecated/discovery-digest.js} +0 -0
- /package/tools/{discovery-hub.js → _deprecated/discovery-hub.js} +0 -0
- /package/tools/{discovery-insights.js → _deprecated/discovery-insights.js} +0 -0
- /package/tools/{discovery-momentum.js → _deprecated/discovery-momentum.js} +0 -0
- /package/tools/{discovery-monitor.js → _deprecated/discovery-monitor.js} +0 -0
- /package/tools/{discovery-proactive.js → _deprecated/discovery-proactive.js} +0 -0
- /package/tools/{draw.js → _deprecated/draw.js} +0 -0
- /package/tools/{farcaster.js → _deprecated/farcaster.js} +0 -0
- /package/tools/{forget.js → _deprecated/forget.js} +0 -0
- /package/tools/{games-catalog.js → _deprecated/games-catalog.js} +0 -0
- /package/tools/{games.js → _deprecated/games.js} +0 -0
- /package/tools/{guessnumber.js → _deprecated/guessnumber.js} +0 -0
- /package/tools/{hangman.js → _deprecated/hangman.js} +0 -0
- /package/tools/{multiplayer-tictactoe.js → _deprecated/multiplayer-tictactoe.js} +0 -0
- /package/tools/{mute.js → _deprecated/mute.js} +0 -0
- /package/tools/{recall.js → _deprecated/recall.js} +0 -0
- /package/tools/{remember.js → _deprecated/remember.js} +0 -0
- /package/tools/{riddle.js → _deprecated/riddle.js} +0 -0
- /package/tools/{run-bootstrap.js → _deprecated/run-bootstrap.js} +0 -0
- /package/tools/{skills-analytics.js → _deprecated/skills-analytics.js} +0 -0
- /package/tools/{skills-bootstrap.js → _deprecated/skills-bootstrap.js} +0 -0
- /package/tools/{skills-dashboard.js → _deprecated/skills-dashboard.js} +0 -0
- /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
- /package/tools/{skills.js → _deprecated/skills.js} +0 -0
- /package/tools/{smart-intro.js → _deprecated/smart-intro.js} +0 -0
- /package/tools/{storybuilder.js → _deprecated/storybuilder.js} +0 -0
- /package/tools/{telegram-bot.js → _deprecated/telegram-bot.js} +0 -0
- /package/tools/{telegram-setup.js → _deprecated/telegram-setup.js} +0 -0
- /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
- /package/tools/{twentyquestions.js → _deprecated/twentyquestions.js} +0 -0
- /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
package/store/profiles.js
CHANGED
|
@@ -58,7 +58,7 @@ async function getProfile(handle) {
|
|
|
58
58
|
async function updateProfile(handle, updates) {
|
|
59
59
|
const profiles = loadProfiles();
|
|
60
60
|
const key = handle.toLowerCase().replace('@', '');
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
const existing = profiles[key] || {
|
|
63
63
|
handle: key,
|
|
64
64
|
building: null,
|
|
@@ -72,29 +72,33 @@ async function updateProfile(handle, updates) {
|
|
|
72
72
|
|
|
73
73
|
// Merge updates
|
|
74
74
|
const updated = { ...existing, ...updates };
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
// Ensure arrays are properly formatted
|
|
77
77
|
if (updates.interests) {
|
|
78
|
-
updated.interests = Array.isArray(updates.interests)
|
|
79
|
-
? updates.interests
|
|
78
|
+
updated.interests = Array.isArray(updates.interests)
|
|
79
|
+
? updates.interests
|
|
80
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;
|
|
81
85
|
}
|
|
82
|
-
|
|
86
|
+
|
|
83
87
|
if (updates.tags) {
|
|
84
88
|
updated.tags = Array.isArray(updates.tags)
|
|
85
89
|
? updates.tags
|
|
86
90
|
: updates.tags.split(',').map(s => s.trim()).filter(s => s);
|
|
87
91
|
}
|
|
88
|
-
|
|
92
|
+
|
|
89
93
|
// Update timestamps
|
|
90
94
|
updated.lastSeen = Date.now();
|
|
91
95
|
if (!existing.firstSeen) {
|
|
92
96
|
updated.firstSeen = Date.now();
|
|
93
97
|
}
|
|
94
|
-
|
|
98
|
+
|
|
95
99
|
profiles[key] = updated;
|
|
96
100
|
saveProfiles(profiles);
|
|
97
|
-
|
|
101
|
+
|
|
98
102
|
return updated;
|
|
99
103
|
}
|
|
100
104
|
|
|
@@ -254,7 +258,7 @@ function createEmptyProfile(handle) {
|
|
|
254
258
|
async function cleanupOldProfiles(daysThreshold = 30) {
|
|
255
259
|
const profiles = loadProfiles();
|
|
256
260
|
const cutoff = Date.now() - (daysThreshold * 24 * 60 * 60 * 1000);
|
|
257
|
-
|
|
261
|
+
|
|
258
262
|
let cleaned = 0;
|
|
259
263
|
for (const [key, profile] of Object.entries(profiles)) {
|
|
260
264
|
if (profile.lastSeen && profile.lastSeen < cutoff) {
|
|
@@ -262,14 +266,154 @@ async function cleanupOldProfiles(daysThreshold = 30) {
|
|
|
262
266
|
cleaned++;
|
|
263
267
|
}
|
|
264
268
|
}
|
|
265
|
-
|
|
269
|
+
|
|
266
270
|
if (cleaned > 0) {
|
|
267
271
|
saveProfiles(profiles);
|
|
268
272
|
}
|
|
269
|
-
|
|
273
|
+
|
|
270
274
|
return cleaned;
|
|
271
275
|
}
|
|
272
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
|
+
|
|
273
417
|
module.exports = {
|
|
274
418
|
getProfile,
|
|
275
419
|
updateProfile,
|
|
@@ -283,5 +427,9 @@ module.exports = {
|
|
|
283
427
|
getUsersByTag,
|
|
284
428
|
getTrendingInterests,
|
|
285
429
|
getTrendingTags,
|
|
286
|
-
cleanupOldProfiles
|
|
430
|
+
cleanupOldProfiles,
|
|
431
|
+
// Presence sync (Phase 1)
|
|
432
|
+
syncFromPresence,
|
|
433
|
+
inferMissingInterests,
|
|
434
|
+
getProfilesWithInterests
|
|
287
435
|
};
|
package/tools/_actions.js
CHANGED
|
@@ -45,9 +45,21 @@ async function dm_user(handle, message) {
|
|
|
45
45
|
const actions = {
|
|
46
46
|
// After vibe_start or vibe_who
|
|
47
47
|
dashboard: (context = {}) => {
|
|
48
|
-
const { unreadCount = 0, onlineUsers = [], suggestion } = context;
|
|
48
|
+
const { unreadCount = 0, onlineUsers = [], suggestion, workContext } = context;
|
|
49
49
|
const result = [];
|
|
50
50
|
|
|
51
|
+
// Priority 0: If we have work context, offer to share it
|
|
52
|
+
if (workContext?.summary) {
|
|
53
|
+
const shortSummary = workContext.summary.length > 40
|
|
54
|
+
? workContext.summary.slice(0, 40) + '...'
|
|
55
|
+
: workContext.summary;
|
|
56
|
+
result.push({
|
|
57
|
+
label: `Share: "${shortSummary}"`,
|
|
58
|
+
description: 'Post what you\'re working on',
|
|
59
|
+
command: `ship: ${workContext.summary}`
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
51
63
|
// Priority 1: Unread messages
|
|
52
64
|
if (unreadCount > 0) {
|
|
53
65
|
result.push({
|
|
@@ -222,6 +234,11 @@ const actions = {
|
|
|
222
234
|
|
|
223
235
|
// After sending a DM
|
|
224
236
|
afterDm: (handle) => [
|
|
237
|
+
{
|
|
238
|
+
label: 'Reply + tip $1',
|
|
239
|
+
description: `Send reply with $1 USDC tip to @${handle}`,
|
|
240
|
+
command: `reply @${handle} with tip`
|
|
241
|
+
},
|
|
225
242
|
{
|
|
226
243
|
label: 'Send another',
|
|
227
244
|
description: `Continue conversation with @${handle}`,
|
|
@@ -233,9 +250,28 @@ const actions = {
|
|
|
233
250
|
command: `react to @${handle}`
|
|
234
251
|
},
|
|
235
252
|
{
|
|
236
|
-
label: '
|
|
237
|
-
description:
|
|
238
|
-
command:
|
|
253
|
+
label: 'Find more people',
|
|
254
|
+
description: 'Discover other connections',
|
|
255
|
+
command: 'discover suggest'
|
|
256
|
+
}
|
|
257
|
+
],
|
|
258
|
+
|
|
259
|
+
// After sending a tip
|
|
260
|
+
afterTip: (handle) => [
|
|
261
|
+
{
|
|
262
|
+
label: 'Send message',
|
|
263
|
+
description: `Message @${handle}`,
|
|
264
|
+
command: `message @${handle}`
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
label: 'Tip again',
|
|
268
|
+
description: `Send another tip to @${handle}`,
|
|
269
|
+
command: `tip @${handle}`
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
label: 'View their work',
|
|
273
|
+
description: `See @${handle}'s proof of work`,
|
|
274
|
+
command: `show proof of work for @${handle}`
|
|
239
275
|
},
|
|
240
276
|
{
|
|
241
277
|
label: 'Find more people',
|
|
@@ -244,6 +280,110 @@ const actions = {
|
|
|
244
280
|
}
|
|
245
281
|
],
|
|
246
282
|
|
|
283
|
+
// After viewing a ship in the feed
|
|
284
|
+
afterShipView: (ship) => {
|
|
285
|
+
const author = ship?.author || 'creator';
|
|
286
|
+
return [
|
|
287
|
+
{
|
|
288
|
+
label: 'Tip $1 for this ship',
|
|
289
|
+
description: `Thank @${author} for shipping`,
|
|
290
|
+
command: `tip @${author} 1 for ship`
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
label: `Message @${author}`,
|
|
294
|
+
description: 'Start a conversation',
|
|
295
|
+
command: `message @${author}`
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
label: 'React 🔥',
|
|
299
|
+
description: 'Show some love',
|
|
300
|
+
command: `react fire to @${author}`
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
label: 'See more ships',
|
|
304
|
+
description: 'Browse the feed',
|
|
305
|
+
command: 'show the feed'
|
|
306
|
+
}
|
|
307
|
+
];
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
// After viewing someone's proof-of-work profile
|
|
311
|
+
afterProofOfWork: (handle, isSelf = false) => {
|
|
312
|
+
if (isSelf) {
|
|
313
|
+
return [
|
|
314
|
+
{
|
|
315
|
+
label: 'Post a ship',
|
|
316
|
+
description: 'Share what you\'re building',
|
|
317
|
+
command: 'ship something'
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
label: 'Browse gigs',
|
|
321
|
+
description: 'Find work opportunities',
|
|
322
|
+
command: 'browse gigs'
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
label: 'Set availability',
|
|
326
|
+
description: 'Update your hire status',
|
|
327
|
+
command: 'update availability'
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
label: 'View earnings',
|
|
331
|
+
description: 'Check your earnings dashboard',
|
|
332
|
+
command: 'show my earnings'
|
|
333
|
+
}
|
|
334
|
+
];
|
|
335
|
+
}
|
|
336
|
+
return [
|
|
337
|
+
{
|
|
338
|
+
label: `Tip @${handle}`,
|
|
339
|
+
description: 'Send a tip to support their work',
|
|
340
|
+
command: `tip @${handle}`
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
label: `Message @${handle}`,
|
|
344
|
+
description: 'Start a conversation',
|
|
345
|
+
command: `message @${handle}`
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
label: 'View their ships',
|
|
349
|
+
description: `See what @${handle} has shipped`,
|
|
350
|
+
command: `show feed from @${handle}`
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
label: 'Find similar builders',
|
|
354
|
+
description: 'Discover people like them',
|
|
355
|
+
command: 'discover suggest'
|
|
356
|
+
}
|
|
357
|
+
];
|
|
358
|
+
},
|
|
359
|
+
|
|
360
|
+
// After completing a gig
|
|
361
|
+
afterGigComplete: (gig) => {
|
|
362
|
+
const worker = gig?.hired_handle || 'worker';
|
|
363
|
+
return [
|
|
364
|
+
{
|
|
365
|
+
label: 'Add $5 bonus tip',
|
|
366
|
+
description: `Extra thanks to @${worker} for great work`,
|
|
367
|
+
command: `tip @${worker} 5 gig bonus`
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
label: `Message @${worker}`,
|
|
371
|
+
description: 'Send a thank you message',
|
|
372
|
+
command: `message @${worker}`
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
label: 'Post another gig',
|
|
376
|
+
description: 'Create a new gig listing',
|
|
377
|
+
command: 'post a gig'
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
label: 'Browse gigs',
|
|
381
|
+
description: 'See other opportunities',
|
|
382
|
+
command: 'browse gigs'
|
|
383
|
+
}
|
|
384
|
+
];
|
|
385
|
+
},
|
|
386
|
+
|
|
247
387
|
// After checking inbox
|
|
248
388
|
afterInbox: (threads = []) => {
|
|
249
389
|
const result = [];
|
|
@@ -316,28 +456,54 @@ const actions = {
|
|
|
316
456
|
},
|
|
317
457
|
|
|
318
458
|
// When room is empty
|
|
319
|
-
emptyRoom: () =>
|
|
320
|
-
{
|
|
459
|
+
emptyRoom: (context = {}) => {
|
|
460
|
+
const { workContext } = context;
|
|
461
|
+
const result = [];
|
|
462
|
+
|
|
463
|
+
// If we have work context, lead with "share your progress"
|
|
464
|
+
if (workContext?.summary) {
|
|
465
|
+
const shortSummary = workContext.summary.length > 40
|
|
466
|
+
? workContext.summary.slice(0, 40) + '...'
|
|
467
|
+
: workContext.summary;
|
|
468
|
+
result.push({
|
|
469
|
+
label: `Ship: "${shortSummary}"`,
|
|
470
|
+
description: 'Share your progress to the board',
|
|
471
|
+
command: `ship: ${workContext.summary}`
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
result.push({
|
|
321
476
|
label: 'Find your people',
|
|
322
477
|
description: 'Discover builders with similar interests',
|
|
323
478
|
command: 'discover suggest'
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// Only show "Set up profile" if we didn't already add work context
|
|
482
|
+
if (!workContext?.summary) {
|
|
483
|
+
result.push({
|
|
484
|
+
label: 'Set up profile',
|
|
485
|
+
description: 'Add interests to get matched',
|
|
486
|
+
command: 'update my profile'
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
result.push({
|
|
331
491
|
label: 'Invite someone',
|
|
332
492
|
description: 'Generate a shareable invite link',
|
|
333
493
|
command: 'generate invite link'
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// Only add generic "Post to board" if we don't have specific work context
|
|
497
|
+
if (!workContext?.summary) {
|
|
498
|
+
result.push({
|
|
499
|
+
label: 'Post to board',
|
|
500
|
+
description: 'Share what you\'re building',
|
|
501
|
+
command: 'post to the vibe board'
|
|
502
|
+
});
|
|
339
503
|
}
|
|
340
|
-
|
|
504
|
+
|
|
505
|
+
return result.slice(0, 4);
|
|
506
|
+
},
|
|
341
507
|
|
|
342
508
|
// When inbox is empty (all caught up) - RETENTION OPTIMIZED
|
|
343
509
|
emptyInbox: (context = {}) => {
|
|
@@ -398,7 +564,7 @@ const actions = {
|
|
|
398
564
|
newUser: () => [
|
|
399
565
|
{
|
|
400
566
|
label: 'Check messages (Recommended)',
|
|
401
|
-
description: '@
|
|
567
|
+
description: '@seth sent you a personalized welcome!',
|
|
402
568
|
command: 'check my messages'
|
|
403
569
|
},
|
|
404
570
|
{
|
|
@@ -437,6 +603,55 @@ const actions = {
|
|
|
437
603
|
}
|
|
438
604
|
],
|
|
439
605
|
|
|
606
|
+
// Recommended connections for empty inbox (personalized message options)
|
|
607
|
+
// matches should include: handle, building, reasons, statusIcon, statusLabel
|
|
608
|
+
recommendedConnections: (matches = []) => {
|
|
609
|
+
const result = [];
|
|
610
|
+
|
|
611
|
+
// Top 3 matches become direct "Message @person" options
|
|
612
|
+
matches.slice(0, 3).forEach(match => {
|
|
613
|
+
// Build rich description: status + building + reason
|
|
614
|
+
const parts = [];
|
|
615
|
+
|
|
616
|
+
// Status (e.g., "🔥 shipping")
|
|
617
|
+
if (match.statusIcon && match.statusLabel) {
|
|
618
|
+
parts.push(`${match.statusIcon} ${match.statusLabel}`);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Building (e.g., "Building AI code reviewer")
|
|
622
|
+
if (match.building) {
|
|
623
|
+
const shortBuilding = match.building.length > 35
|
|
624
|
+
? match.building.slice(0, 35) + '...'
|
|
625
|
+
: match.building;
|
|
626
|
+
parts.push(`"${shortBuilding}"`);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// First reason (e.g., "Shared interest: AI")
|
|
630
|
+
if (match.reasons?.[0]) {
|
|
631
|
+
parts.push(match.reasons[0]);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const description = parts.length > 0
|
|
635
|
+
? parts.join(' • ')
|
|
636
|
+
: 'Connect with them';
|
|
637
|
+
|
|
638
|
+
result.push({
|
|
639
|
+
label: `Message @${match.handle}`,
|
|
640
|
+
description,
|
|
641
|
+
command: `message @${match.handle}`
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
// 4th option: Discover more people
|
|
646
|
+
result.push({
|
|
647
|
+
label: 'Discover more people',
|
|
648
|
+
description: 'See more recommendations',
|
|
649
|
+
command: 'discover suggest'
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
return result.slice(0, 4);
|
|
653
|
+
},
|
|
654
|
+
|
|
440
655
|
// Reaction selection
|
|
441
656
|
reactionOptions: (handle) => [
|
|
442
657
|
{
|
|
@@ -459,7 +674,133 @@ const actions = {
|
|
|
459
674
|
description: 'Smart thinking',
|
|
460
675
|
command: `react brain to @${handle}`
|
|
461
676
|
}
|
|
462
|
-
]
|
|
677
|
+
],
|
|
678
|
+
|
|
679
|
+
// After opening a thread - smart pre-drafted replies
|
|
680
|
+
// Takes the last message from the other person and generates contextual reply options
|
|
681
|
+
afterOpenThread: (handle, lastMessage = '', context = {}) => {
|
|
682
|
+
const result = [];
|
|
683
|
+
const msg = (lastMessage || '').toLowerCase();
|
|
684
|
+
|
|
685
|
+
// Detect message tone/type and generate appropriate replies
|
|
686
|
+
const isQuestion = msg.includes('?') ||
|
|
687
|
+
msg.startsWith('what') ||
|
|
688
|
+
msg.startsWith('how') ||
|
|
689
|
+
msg.startsWith('can you') ||
|
|
690
|
+
msg.startsWith('could you') ||
|
|
691
|
+
msg.startsWith('do you') ||
|
|
692
|
+
msg.startsWith('would you');
|
|
693
|
+
|
|
694
|
+
const isShipping = msg.includes('shipped') ||
|
|
695
|
+
msg.includes('deployed') ||
|
|
696
|
+
msg.includes('launched') ||
|
|
697
|
+
msg.includes('just pushed') ||
|
|
698
|
+
msg.includes('it\'s live') ||
|
|
699
|
+
msg.includes('check out') ||
|
|
700
|
+
msg.includes('built');
|
|
701
|
+
|
|
702
|
+
const isOfferingHelp = msg.includes('let me know') ||
|
|
703
|
+
msg.includes('i can help') ||
|
|
704
|
+
msg.includes('happy to') ||
|
|
705
|
+
msg.includes('i\'ll handle');
|
|
706
|
+
|
|
707
|
+
const isAskingForHelp = msg.includes('help') ||
|
|
708
|
+
msg.includes('stuck') ||
|
|
709
|
+
msg.includes('issue') ||
|
|
710
|
+
msg.includes('problem') ||
|
|
711
|
+
msg.includes('bug');
|
|
712
|
+
|
|
713
|
+
const isUpdate = msg.includes('update') ||
|
|
714
|
+
msg.includes('progress') ||
|
|
715
|
+
msg.includes('working on') ||
|
|
716
|
+
msg.includes('gonna') ||
|
|
717
|
+
msg.includes('going to');
|
|
718
|
+
|
|
719
|
+
// Generate 2 contextual reply drafts based on tone
|
|
720
|
+
// Command format: "reply @handle: <message>" triggers compose flow
|
|
721
|
+
if (isQuestion) {
|
|
722
|
+
// They asked a question - suggest helpful answers
|
|
723
|
+
result.push({
|
|
724
|
+
label: '"Yeah, happy to help with that!"',
|
|
725
|
+
description: 'Positive response',
|
|
726
|
+
command: `reply @${handle}: Yeah, happy to help with that!`
|
|
727
|
+
});
|
|
728
|
+
result.push({
|
|
729
|
+
label: '"Let me look into it and get back to you"',
|
|
730
|
+
description: 'Need time to check',
|
|
731
|
+
command: `reply @${handle}: Let me look into it and get back to you`
|
|
732
|
+
});
|
|
733
|
+
} else if (isShipping) {
|
|
734
|
+
// They shipped something - celebrate!
|
|
735
|
+
result.push({
|
|
736
|
+
label: '"Nice! 🚀 Congrats on shipping!"',
|
|
737
|
+
description: 'Celebrate their ship',
|
|
738
|
+
command: `reply @${handle}: Nice! 🚀 Congrats on shipping!`
|
|
739
|
+
});
|
|
740
|
+
result.push({
|
|
741
|
+
label: '"That\'s awesome, checking it out now!"',
|
|
742
|
+
description: 'Show interest',
|
|
743
|
+
command: `reply @${handle}: That's awesome, checking it out now!`
|
|
744
|
+
});
|
|
745
|
+
} else if (isOfferingHelp) {
|
|
746
|
+
// They're offering to help - accept gracefully
|
|
747
|
+
result.push({
|
|
748
|
+
label: '"Perfect, thanks for taking that on! 🙌"',
|
|
749
|
+
description: 'Accept their help',
|
|
750
|
+
command: `reply @${handle}: Perfect, thanks for taking that on! 🙌`
|
|
751
|
+
});
|
|
752
|
+
result.push({
|
|
753
|
+
label: '"Appreciate it! LMK if you need anything from me"',
|
|
754
|
+
description: 'Grateful + offer support back',
|
|
755
|
+
command: `reply @${handle}: Appreciate it! LMK if you need anything from me`
|
|
756
|
+
});
|
|
757
|
+
} else if (isAskingForHelp) {
|
|
758
|
+
// They need help - offer assistance
|
|
759
|
+
result.push({
|
|
760
|
+
label: '"I can take a look - what have you tried?"',
|
|
761
|
+
description: 'Offer to help',
|
|
762
|
+
command: `reply @${handle}: I can take a look - what have you tried so far?`
|
|
763
|
+
});
|
|
764
|
+
result.push({
|
|
765
|
+
label: '"Want to hop on a quick call to debug?"',
|
|
766
|
+
description: 'Offer pairing session',
|
|
767
|
+
command: `reply @${handle}: Want to hop on a quick call to debug together?`
|
|
768
|
+
});
|
|
769
|
+
} else if (isUpdate) {
|
|
770
|
+
// General update - acknowledge
|
|
771
|
+
result.push({
|
|
772
|
+
label: '"Sounds good, thanks for the update!"',
|
|
773
|
+
description: 'Acknowledge',
|
|
774
|
+
command: `reply @${handle}: Sounds good, thanks for the update!`
|
|
775
|
+
});
|
|
776
|
+
result.push({
|
|
777
|
+
label: '"Nice! LMK if you need anything"',
|
|
778
|
+
description: 'Supportive acknowledgment',
|
|
779
|
+
command: `reply @${handle}: Nice! LMK if you need anything`
|
|
780
|
+
});
|
|
781
|
+
} else {
|
|
782
|
+
// Default fallback - general positive responses
|
|
783
|
+
result.push({
|
|
784
|
+
label: '"Sounds good! 👍"',
|
|
785
|
+
description: 'Quick acknowledgment',
|
|
786
|
+
command: `reply @${handle}: Sounds good! 👍`
|
|
787
|
+
});
|
|
788
|
+
result.push({
|
|
789
|
+
label: '"Thanks for letting me know!"',
|
|
790
|
+
description: 'Grateful response',
|
|
791
|
+
command: `reply @${handle}: Thanks for letting me know!`
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// 3rd option: escape to something else (feed or discover)
|
|
796
|
+
result.push({
|
|
797
|
+
label: 'Browse the feed',
|
|
798
|
+
description: 'See what people are shipping',
|
|
799
|
+
command: 'show the feed'
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
return result.slice(0, 3);
|
|
803
|
+
}
|
|
463
804
|
};
|
|
464
805
|
|
|
465
806
|
// Format actions for the response object
|