slashvibe-mcp 0.3.20 → 0.3.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +47 -252
  2. package/analytics.js +107 -0
  3. package/auth-store.js +148 -0
  4. package/auto-update.js +130 -0
  5. package/bridges/bridge-monitor.js +388 -0
  6. package/bridges/discord-bot.js +431 -0
  7. package/bridges/farcaster.js +299 -0
  8. package/bridges/telegram.js +261 -0
  9. package/bridges/webhook-health.js +420 -0
  10. package/bridges/webhook-server.js +437 -0
  11. package/bridges/whatsapp.js +441 -0
  12. package/bridges/x-webhook.js +423 -0
  13. package/config.js +27 -15
  14. package/games/arcade.js +406 -0
  15. package/games/chess.js +451 -0
  16. package/games/colorguess.js +343 -0
  17. package/games/crossword-words.js +171 -0
  18. package/games/crossword.js +461 -0
  19. package/games/drawing.js +347 -0
  20. package/games/gameroulette.js +300 -0
  21. package/games/gamerouter.js +336 -0
  22. package/games/gamestatus.js +337 -0
  23. package/games/guessnumber.js +209 -0
  24. package/games/hangman.js +279 -0
  25. package/games/memory.js +338 -0
  26. package/games/multiplayer-tictactoe.js +389 -0
  27. package/games/pixelart.js +399 -0
  28. package/games/quickduel.js +354 -0
  29. package/games/riddle.js +371 -0
  30. package/games/rockpaperscissors.js +291 -0
  31. package/games/snake.js +406 -0
  32. package/games/storybuilder.js +343 -0
  33. package/games/tictactoe.js +345 -0
  34. package/games/twentyquestions.js +286 -0
  35. package/games/twotruths.js +207 -0
  36. package/games/werewolf.js +508 -0
  37. package/games/wordassociation.js +247 -0
  38. package/games/wordchain.js +135 -0
  39. package/index.js +116 -159
  40. package/intelligence/index.js +9 -2
  41. package/intelligence/interests.js +369 -0
  42. package/notification-emitter.js +77 -0
  43. package/notify.js +5 -1
  44. package/package.json +21 -16
  45. package/prompts.js +1 -1
  46. package/protocol/index.js +73 -0
  47. package/setup.js +480 -0
  48. package/smart-inbox.js +276 -0
  49. package/store/api.js +536 -215
  50. package/store/profiles.js +160 -12
  51. package/tools/_actions.js +362 -21
  52. package/tools/_discovery.js +119 -26
  53. package/tools/_shared/index.js +64 -0
  54. package/tools/_shared.js +234 -0
  55. package/tools/_work-context.js +338 -0
  56. package/tools/_work-context.manual-test.js +199 -0
  57. package/tools/_work-context.test.js +260 -0
  58. package/tools/activity.js +220 -0
  59. package/tools/analytics.js +191 -0
  60. package/tools/approve.js +197 -0
  61. package/tools/artifact-create.js +14 -3
  62. package/tools/artifacts-price.js +107 -0
  63. package/tools/available.js +120 -0
  64. package/tools/broadcast.js +325 -0
  65. package/tools/chat.js +202 -0
  66. package/tools/collaborative-drawing.js +1 -1
  67. package/tools/connection-status.js +178 -0
  68. package/tools/discover.js +350 -34
  69. package/tools/dm.js +80 -8
  70. package/tools/earnings.js +126 -0
  71. package/tools/feed.js +35 -4
  72. package/tools/follow.js +224 -0
  73. package/tools/friends.js +207 -0
  74. package/tools/gig-browse.js +206 -0
  75. package/tools/gig-complete.js +144 -0
  76. package/tools/health.js +87 -0
  77. package/tools/help.js +3 -3
  78. package/tools/idea.js +9 -2
  79. package/tools/inbox.js +289 -105
  80. package/tools/init.js +131 -34
  81. package/tools/invite.js +15 -4
  82. package/tools/leaderboard.js +117 -0
  83. package/tools/lib/git-apply.js +206 -0
  84. package/tools/lib/git-bundle.js +407 -0
  85. package/tools/migrate.js +3 -3
  86. package/tools/multiplayer-game.js +1 -1
  87. package/tools/onboarding.js +7 -7
  88. package/tools/open.js +143 -12
  89. package/tools/party-game.js +1 -1
  90. package/tools/plan.js +225 -0
  91. package/tools/proof-of-work.js +144 -0
  92. package/tools/reply.js +166 -0
  93. package/tools/report.js +1 -1
  94. package/tools/request.js +17 -3
  95. package/tools/schedule.js +367 -0
  96. package/tools/search-messages.js +123 -0
  97. package/tools/session.js +467 -0
  98. package/tools/session_price.js +128 -0
  99. package/tools/settings.js +90 -2
  100. package/tools/ship.js +30 -7
  101. package/tools/smart-check.js +201 -0
  102. package/tools/start.js +147 -12
  103. package/tools/status.js +53 -6
  104. package/tools/streak.js +147 -0
  105. package/tools/stuck.js +297 -0
  106. package/tools/subscribe.js +148 -0
  107. package/tools/subscriptions.js +134 -0
  108. package/tools/suggest-tags.js +6 -8
  109. package/tools/tag-suggestions.js +1 -1
  110. package/tools/tip.js +150 -77
  111. package/tools/token.js +4 -4
  112. package/tools/update.js +1 -1
  113. package/tools/wallet.js +221 -79
  114. package/tools/watch.js +157 -0
  115. package/tools/who.js +30 -1
  116. package/tools/withdraw.js +145 -0
  117. package/tools/work-summary.js +96 -0
  118. package/version.json +10 -8
  119. package/LICENSE +0 -21
  120. package/store/sqlite.js +0 -347
  121. /package/tools/{auto-suggest-connections.js → _deprecated/auto-suggest-connections.js} +0 -0
  122. /package/tools/{away.js → _deprecated/away.js} +0 -0
  123. /package/tools/{back.js → _deprecated/back.js} +0 -0
  124. /package/tools/{bootstrap-skills.js → _deprecated/bootstrap-skills.js} +0 -0
  125. /package/tools/{bridge-dashboard.js → _deprecated/bridge-dashboard.js} +0 -0
  126. /package/tools/{bridge-health.js → _deprecated/bridge-health.js} +0 -0
  127. /package/tools/{bridge-live.js → _deprecated/bridge-live.js} +0 -0
  128. /package/tools/{bridges.js → _deprecated/bridges.js} +0 -0
  129. /package/tools/{colorguess.js → _deprecated/colorguess.js} +0 -0
  130. /package/tools/{discover-insights.js → _deprecated/discover-insights.js} +0 -0
  131. /package/tools/{discover-momentum.js → _deprecated/discover-momentum.js} +0 -0
  132. /package/tools/{discovery-analytics.js → _deprecated/discovery-analytics.js} +0 -0
  133. /package/tools/{discovery-auto-suggest.js → _deprecated/discovery-auto-suggest.js} +0 -0
  134. /package/tools/{discovery-bootstrap.js → _deprecated/discovery-bootstrap.js} +0 -0
  135. /package/tools/{discovery-daily.js → _deprecated/discovery-daily.js} +0 -0
  136. /package/tools/{discovery-dashboard.js → _deprecated/discovery-dashboard.js} +0 -0
  137. /package/tools/{discovery-digest.js → _deprecated/discovery-digest.js} +0 -0
  138. /package/tools/{discovery-hub.js → _deprecated/discovery-hub.js} +0 -0
  139. /package/tools/{discovery-insights.js → _deprecated/discovery-insights.js} +0 -0
  140. /package/tools/{discovery-momentum.js → _deprecated/discovery-momentum.js} +0 -0
  141. /package/tools/{discovery-monitor.js → _deprecated/discovery-monitor.js} +0 -0
  142. /package/tools/{discovery-proactive.js → _deprecated/discovery-proactive.js} +0 -0
  143. /package/tools/{draw.js → _deprecated/draw.js} +0 -0
  144. /package/tools/{farcaster.js → _deprecated/farcaster.js} +0 -0
  145. /package/tools/{forget.js → _deprecated/forget.js} +0 -0
  146. /package/tools/{games-catalog.js → _deprecated/games-catalog.js} +0 -0
  147. /package/tools/{games.js → _deprecated/games.js} +0 -0
  148. /package/tools/{guessnumber.js → _deprecated/guessnumber.js} +0 -0
  149. /package/tools/{hangman.js → _deprecated/hangman.js} +0 -0
  150. /package/tools/{multiplayer-tictactoe.js → _deprecated/multiplayer-tictactoe.js} +0 -0
  151. /package/tools/{mute.js → _deprecated/mute.js} +0 -0
  152. /package/tools/{recall.js → _deprecated/recall.js} +0 -0
  153. /package/tools/{remember.js → _deprecated/remember.js} +0 -0
  154. /package/tools/{riddle.js → _deprecated/riddle.js} +0 -0
  155. /package/tools/{run-bootstrap.js → _deprecated/run-bootstrap.js} +0 -0
  156. /package/tools/{skills-analytics.js → _deprecated/skills-analytics.js} +0 -0
  157. /package/tools/{skills-bootstrap.js → _deprecated/skills-bootstrap.js} +0 -0
  158. /package/tools/{skills-dashboard.js → _deprecated/skills-dashboard.js} +0 -0
  159. /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
  160. /package/tools/{skills.js → _deprecated/skills.js} +0 -0
  161. /package/tools/{smart-intro.js → _deprecated/smart-intro.js} +0 -0
  162. /package/tools/{storybuilder.js → _deprecated/storybuilder.js} +0 -0
  163. /package/tools/{telegram-bot.js → _deprecated/telegram-bot.js} +0 -0
  164. /package/tools/{telegram-setup.js → _deprecated/telegram-setup.js} +0 -0
  165. /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
  166. /package/tools/{twentyquestions.js → _deprecated/twentyquestions.js} +0 -0
  167. /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
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: 'Remember something',
237
- description: `Save a note about @${handle}`,
238
- command: `remember something about @${handle}`
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
- label: 'Set up profile',
327
- description: 'Add interests to get matched',
328
- command: 'update my profile'
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
- label: 'Post to board',
337
- description: 'Share what you\'re building',
338
- command: 'post to the vibe board'
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: '@vibe sent you a personalized welcome!',
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