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/tools/inbox.js CHANGED
@@ -5,118 +5,59 @@
5
5
  const config = require('../config');
6
6
  const store = require('../store');
7
7
  const notify = require('../notify');
8
- const patterns = require('../intelligence/patterns');
9
- const { formatPayload } = require('../protocol');
10
- const { requireInit, header, emptyState, formatTimeAgo, truncate, divider, fetchRelevantUsers } = require('./_shared');
8
+ const analytics = require('../analytics');
9
+ const { requireInit, header, emptyState, formatTimeAgo, truncate, divider } = require('./_shared');
11
10
  const { actions, formatActions } = require('./_actions');
12
11
 
13
- // Truncate message for preview (first 100 chars, clean break at word)
14
- function summarizeMessage(text, maxLen = 100) {
15
- if (!text || text.length <= maxLen) return text;
16
- const truncated = text.slice(0, maxLen);
17
- const lastSpace = truncated.lastIndexOf(' ');
18
- return (lastSpace > maxLen * 0.7 ? truncated.slice(0, lastSpace) : truncated) + '...';
19
- }
20
-
21
- // Get activity heat icon for a user (borrowed from who.js logic)
22
- function getStatusIcon(user) {
23
- if (!user) return '●';
24
-
25
- // Check mood/status
26
- if (user.mood === '🔥' || user.mood === '🚀' || user.builderMode === 'shipping') return '🔥';
27
- if (user.mood === '🧠' || user.builderMode === 'deep-focus') return '🧠';
28
- if (user.mood === '🐛') return '🐛';
29
- if (user.mood === '🌙') return '🌙';
30
-
31
- // Check recency
32
- const lastSeenMs = user.lastSeen || Date.now();
33
- const minutesAgo = (Date.now() - lastSeenMs) / 60000;
34
- if (minutesAgo < 5) return '⚡';
35
- if (minutesAgo < 30) return '●';
36
- return '○';
37
- }
38
-
39
- // Get status label for a user
40
- function getStatusLabel(user) {
41
- if (!user) return null;
42
-
43
- if (user.mood === '🔥' || user.mood === '🚀' || user.builderMode === 'shipping') return 'shipping';
44
- if (user.mood === '🧠' || user.builderMode === 'deep-focus') return 'deep focus';
45
- if (user.mood === '🐛') return 'debugging';
46
- if (user.mood === '🌙') return 'late night';
47
- if (user.mood === '💭') return 'thinking';
48
-
49
- const lastSeenMs = user.lastSeen || Date.now();
50
- const minutesAgo = (Date.now() - lastSeenMs) / 60000;
51
- if (minutesAgo < 5) return 'active';
52
- if (minutesAgo < 30) return 'online';
53
- return 'away';
54
- }
55
-
56
- // Build recommended connections display for empty/caught-up inbox
57
- async function buildRecommendationsDisplay(myHandle) {
58
- // Fetch recommendations from relevancy API
59
- const relevancy = await fetchRelevantUsers(myHandle, 'dm_suggest', 5);
60
-
61
- if (!relevancy || !relevancy.matches || relevancy.matches.length === 0) {
62
- return null; // Fallback to old behavior
63
- }
64
-
65
- const matches = relevancy.matches;
66
-
67
- // Get presence info for status display
68
- const presenceMap = new Map();
12
+ // Helper: Fetch recent ships for social proof (FOMO)
13
+ async function getRecentShips(limit = 2) {
69
14
  try {
70
- const presence = await store.getPresence();
71
- const allUsers = [...(presence.active || []), ...(presence.away || [])];
72
- allUsers.forEach(u => {
73
- presenceMap.set(u.handle?.toLowerCase(), u);
74
- });
15
+ const apiUrl = config.getApiUrl();
16
+ const response = await fetch(`${apiUrl}/api/board?limit=${limit}&category=shipped`);
17
+ const data = await response.json();
18
+ return (data.entries || []).map(e => ({
19
+ author: e.author,
20
+ content: e.content?.slice(0, 50)
21
+ }));
75
22
  } catch (e) {
76
- // Continue without presence info
23
+ return [];
77
24
  }
25
+ }
78
26
 
79
- // Build preview line (stays visible when collapsed)
80
- const top3Handles = matches.slice(0, 3).map(m => `@${m.handle}`).join(', ');
81
- let display = `📭 All caught up! Connect with: ${top3Handles}\n\n`;
82
-
83
- // Build ranked list with statuses
84
- display += `---\n`;
85
-
86
- matches.forEach(match => {
87
- const presenceUser = presenceMap.get(match.handle?.toLowerCase());
88
- const icon = getStatusIcon(presenceUser);
89
- const statusLabel = getStatusLabel(presenceUser);
90
- const statusText = statusLabel ? ` — ${statusLabel}` : '';
91
-
92
- display += `${icon} **@${match.handle}**${statusText}\n`;
93
-
94
- // Show building + first reason
95
- if (match.building) {
96
- const reason = match.reasons?.[0] ? ` • ${match.reasons[0]}` : '';
97
- display += ` "${summarizeMessage(match.building, 50)}"${reason}\n`;
98
- } else if (match.reasons?.[0]) {
99
- display += ` ${match.reasons[0]}\n`;
27
+ // Helper: Get next incomplete onboarding task
28
+ async function getNextOnboardingTask(handle) {
29
+ try {
30
+ const checklist = await store.getChecklistStatus(handle);
31
+ if (checklist.success && checklist.tasks) {
32
+ const nextTask = checklist.tasks.find(t => !t.done);
33
+ if (nextTask) {
34
+ // Map task IDs to user-friendly actions
35
+ const taskActions = {
36
+ read_welcome: {
37
+ shortLabel: 'Read welcome',
38
+ command: 'check my messages',
39
+ description: 'See your welcome message'
40
+ },
41
+ reply_seth: { shortLabel: 'Reply to @vibe', command: 'message @vibe', description: 'Say hi back!' },
42
+ message_builder: {
43
+ shortLabel: 'Message a builder',
44
+ command: 'discover suggest',
45
+ description: 'Find someone to connect with'
46
+ },
47
+ post_ship: {
48
+ shortLabel: 'Ship something',
49
+ command: 'ship what I built',
50
+ description: "Share what you're building"
51
+ },
52
+ leave_feedback: { shortLabel: 'Give feedback', command: 'talk to @echo', description: 'Help improve /vibe' }
53
+ };
54
+ return taskActions[nextTask.id] || null;
55
+ }
100
56
  }
101
-
102
- display += '\n';
103
- });
104
-
105
- // Enrich matches with status info for action descriptions
106
- const enrichedMatches = matches.map(match => {
107
- const presenceUser = presenceMap.get(match.handle?.toLowerCase());
108
- return {
109
- ...match,
110
- statusIcon: getStatusIcon(presenceUser),
111
- statusLabel: getStatusLabel(presenceUser)
112
- };
113
- });
114
-
115
- return {
116
- display,
117
- matches: enrichedMatches,
118
- actions: formatActions(actions.recommendedConnections(enrichedMatches))
119
- };
57
+ return null;
58
+ } catch (e) {
59
+ return null;
60
+ }
120
61
  }
121
62
 
122
63
  const definition = {
@@ -139,72 +80,48 @@ async function handler(args) {
139
80
  notify.checkAll(store);
140
81
 
141
82
  if (!threads || threads.length === 0) {
142
- // Try to show personalized recommendations
143
- try {
144
- const recommendations = await buildRecommendationsDisplay(myHandle);
145
- if (recommendations) {
146
- // Change preview line for empty inbox (no messages yet)
147
- const display = recommendations.display.replace(
148
- 'All caught up!',
149
- 'No messages yet.'
150
- );
151
- return {
152
- display,
153
- actions: recommendations.actions
154
- };
155
- }
156
- } catch (e) {
157
- console.log('[inbox] recommendations error:', e.message);
83
+ // Fetch context for retention-optimized actions (parallel for speed)
84
+ const [recentShips, onboardingTask] = await Promise.all([getRecentShips(2), getNextOnboardingTask(myHandle)]);
85
+
86
+ // Build social proof line
87
+ let socialProof = '';
88
+ if (recentShips.length > 0) {
89
+ socialProof = `\n 💫 @${recentShips[0].author} just shipped`;
158
90
  }
159
91
 
160
- // Fallback: Check onboarding status to customize CTA
161
- let cta = 'Say "dm @someone" to start';
162
- try {
163
- const checklist = await store.getChecklistStatus(myHandle);
164
- if (checklist.success && checklist.tasks) {
165
- const repliedToSeth = checklist.tasks.find(t => t.id === 'reply_seth')?.done;
166
- if (!repliedToSeth) {
167
- cta = 'Reply to @vibe to get started!';
168
- }
169
- }
170
- } catch (e) {}
92
+ // Build CTA based on onboarding state
93
+ const cta = onboardingTask
94
+ ? `→ ${onboardingTask.shortLabel}: "${onboardingTask.command}"`
95
+ : 'Say "dm @someone" to start';
96
+
97
+ // Track empty inbox state for retention analytics
98
+ analytics.trackEmptyInbox('none', {
99
+ recentThreads: [],
100
+ recentShips,
101
+ onboardingTask,
102
+ state: 'no_messages'
103
+ });
171
104
 
172
105
  return {
173
- display: `📭 No messages yet. ${cta}`,
174
- actions: formatActions(actions.recommendedConnections([]))
106
+ display: `── 📭 Inbox ──────────────────────────
107
+ No messages yet${socialProof}
108
+ ${cta}
109
+ ──────────────────────────────────────`,
110
+ hint: 'suggest_compose',
111
+ actions: formatActions(
112
+ actions.emptyInbox({
113
+ recentThreads: [],
114
+ recentShips,
115
+ onboardingTask
116
+ })
117
+ )
175
118
  };
176
119
  }
177
120
 
178
- // Fetch relevant users to prioritize notifications
179
- // Messages from relevant users appear first within unread threads
180
- const relevantHandles = new Map(); // handle -> relevancy score (position in matches)
181
- try {
182
- const relevancy = await fetchRelevantUsers(myHandle, 'notification', 20);
183
- if (relevancy && relevancy.matches) {
184
- relevancy.matches.forEach((m, idx) => {
185
- // Higher score = more relevant (inverse of position)
186
- relevantHandles.set(m.handle.toLowerCase(), relevancy.matches.length - idx);
187
- });
188
- }
189
- } catch (e) {
190
- // Don't fail inbox if relevancy fails
191
- console.log('[inbox] relevancy fetch error:', e.message);
192
- }
193
-
194
- // Sort: unread first, then by relevancy within unread, then by most recent
121
+ // Sort: unread first, then by most recent
195
122
  const sorted = threads.sort((a, b) => {
196
- // Primary: unread vs read
197
123
  if (a.unread > 0 && b.unread === 0) return -1;
198
124
  if (b.unread > 0 && a.unread === 0) return 1;
199
-
200
- // Secondary (within same read/unread status): relevancy
201
- const aRelevancy = relevantHandles.get(a.handle.toLowerCase()) || 0;
202
- const bRelevancy = relevantHandles.get(b.handle.toLowerCase()) || 0;
203
- if (aRelevancy !== bRelevancy) {
204
- return bRelevancy - aRelevancy; // Higher relevancy first
205
- }
206
-
207
- // Tertiary: timestamp (most recent first)
208
125
  return (b.lastTimestamp || 0) - (a.lastTimestamp || 0);
209
126
  });
210
127
 
@@ -213,141 +130,50 @@ async function handler(args) {
213
130
 
214
131
  // Handle case where all messages are read (no unread)
215
132
  if (totalUnread === 0) {
216
- // Try to show personalized recommendations
217
- try {
218
- const recommendations = await buildRecommendationsDisplay(myHandle);
219
- if (recommendations) {
220
- return {
221
- display: recommendations.display,
222
- actions: recommendations.actions
223
- };
224
- }
225
- } catch (e) {
226
- console.log('[inbox] recommendations error:', e.message);
227
- }
228
-
229
- // Fallback: show recent threads
230
- const recentHandles = sorted.slice(0, 3).map(t => `@${t.handle}`).join(', ');
231
- return {
232
- display: `📭 All caught up! Recent: ${recentHandles}`,
233
- actions: formatActions(actions.recommendedConnections([]))
234
- };
235
- }
236
-
237
- // Auto-open single unread thread inline (no second tool call needed)
238
- if (totalUnread === 1 && unreadSenders.length === 1) {
239
- const them = unreadSenders[0].handle;
240
-
241
- // Fetch full thread and mark as read
242
- const thread = await store.getThread(myHandle, them);
243
- await store.markThreadRead(myHandle, them);
244
-
245
- // Auto-track readWelcomeAt if viewing welcome from @seth
246
- const isWelcomeThread = them.toLowerCase() === 'seth';
247
- if (isWelcomeThread) {
248
- try {
249
- await store.trackChecklistCompletion(myHandle, 'read_welcome', {
250
- source: 'inbox_auto_open',
251
- timestamp: Date.now()
252
- });
253
- console.log('[inbox] Auto-tracked readWelcomeAt for', myHandle);
254
- } catch (e) {
255
- console.warn('[inbox] Failed to track readWelcomeAt:', e.message);
256
- }
257
- }
258
-
259
- // Log received messages for patterns
260
- const theirMessages = thread.filter(m => m.from === them);
261
- if (theirMessages.length > 0) {
262
- patterns.logMessageReceived(them);
263
- }
264
-
265
- // Check if they're typing
266
- let typingNotice = '';
267
- try {
268
- const typingUsers = await store.getTypingUsers(myHandle);
269
- if (typingUsers.includes(them)) {
270
- typingNotice = `\n_@${them} is typing..._\n`;
271
- }
272
- } catch (e) {}
273
-
274
- // Build thread display - summary first, newest-first thread
275
- const latestFromThem = theirMessages.length > 0
276
- ? theirMessages.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0))[0]
277
- : null;
133
+ const recentHandles = sorted.slice(0, 3).map(t => t.handle);
134
+ const recentDisplay = recentHandles.map(h => `@${h}`).join(', ');
278
135
 
279
- let display = '';
136
+ // Fetch context for retention-optimized actions (parallel for speed)
137
+ const [recentShips, onboardingTask] = await Promise.all([getRecentShips(2), getNextOnboardingTask(myHandle)]);
280
138
 
281
- if (latestFromThem) {
282
- const agentBadge = latestFromThem.isAgent ? ' 🤖' : '';
283
- const time = store.formatTimeAgo(latestFromThem.timestamp);
284
- const preview = latestFromThem.body
285
- ? summarizeMessage(latestFromThem.body)
286
- : (latestFromThem.payload ? '[attachment]' : '');
287
-
288
- display = `💬 @${them}${agentBadge} (${time}): "${preview}"\n\n`;
289
- } else {
290
- display = `💬 @${them}: _Waiting for reply..._\n\n`;
139
+ // Build social proof line
140
+ let socialProof = '';
141
+ if (recentShips.length > 0) {
142
+ socialProof = `\n 💫 @${recentShips[0].author} just shipped`;
291
143
  }
292
144
 
293
- // Thread section - sorted newest first
294
- display += `---\n📜 Thread\n\n`;
295
-
296
- const sortedThread = [...thread].sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
297
-
298
- sortedThread.forEach(m => {
299
- const isMe = m.from === myHandle;
300
- const agentBadge = m.isAgent && !isMe ? '🤖 ' : '';
301
- const sender = isMe ? 'you' : `@${m.from}`;
302
- const time = store.formatTimeAgo(m.timestamp);
303
-
304
- display += `${agentBadge}**${sender}** — _${time}_\n`;
305
-
306
- if (m.body) {
307
- display += `${m.body}\n`;
308
- }
309
-
310
- if (m.payload) {
311
- display += `${formatPayload(m.payload)}\n`;
312
- }
313
-
314
- display += '\n';
145
+ // Track empty inbox state for retention analytics
146
+ analytics.trackEmptyInbox('none', {
147
+ recentThreads: recentHandles,
148
+ recentShips,
149
+ onboardingTask,
150
+ state: 'all_caught_up'
315
151
  });
316
152
 
317
- if (typingNotice) {
318
- display += typingNotice + '\n';
319
- }
320
-
321
- display += `---\nJust type your reply to send it`;
322
-
323
- // For @seth welcome thread, fetch recommended builders and add actions
324
- if (isWelcomeThread) {
325
- try {
326
- const onboardingData = await store.getOnboardingData(myHandle);
327
- if (onboardingData.success && onboardingData.recommendedUsers?.length > 0) {
328
- // Build action options to message recommended builders
329
- const recommendedActions = onboardingData.recommendedUsers.slice(0, 3).map(user => {
330
- const description = user.workingOn
331
- ? `Building: "${truncate(user.workingOn, 40)}"`
332
- : 'Recommended for you';
333
- return {
334
- handle: user.handle,
335
- building: user.workingOn,
336
- reasons: ['Matched during your welcome']
337
- };
338
- });
339
-
340
- return {
341
- display,
342
- actions: formatActions(actions.recommendedConnections(recommendedActions))
343
- };
344
- }
345
- } catch (e) {
346
- console.warn('[inbox] Failed to fetch recommended builders:', e.message);
347
- }
348
- }
153
+ return {
154
+ display: `── 📭 Inbox ──────────────────────────
155
+ All caught up! Recent: ${recentDisplay}${socialProof}
156
+ ──────────────────────────────────────`,
157
+ hint: 'suggest_compose',
158
+ actions: formatActions(
159
+ actions.emptyInbox({
160
+ recentThreads: recentHandles,
161
+ recentShips,
162
+ onboardingTask
163
+ })
164
+ )
165
+ };
166
+ }
349
167
 
350
- return { display };
168
+ // Auto-open single unread message (skip inbox view to reduce friction)
169
+ if (totalUnread === 1 && unreadSenders.length === 1) {
170
+ const singleSender = unreadSenders[0];
171
+ return {
172
+ hint: 'auto_open_single_thread',
173
+ handle: singleSender.handle,
174
+ preview: truncate(singleSender.lastMessage || '', 60),
175
+ display: `📬 Opening thread with @${singleSender.handle}...`
176
+ };
351
177
  }
352
178
 
353
179
  // Build compact display (3 lines above the fold)
@@ -363,10 +189,12 @@ async function handler(args) {
363
189
  display += '───────────────────────────────────\n';
364
190
 
365
191
  // Line 4+: Expanded list with counts and badges
366
- const expanded = unreadSenders.map(t => {
367
- const agent = t.isAgent ? ' 🤖' : '';
368
- return `@${t.handle} (${t.unread})${agent}`;
369
- }).join(' • ');
192
+ const expanded = unreadSenders
193
+ .map(t => {
194
+ const agent = t.isAgent ? ' 🤖' : '';
195
+ return `@${t.handle} (${t.unread})${agent}`;
196
+ })
197
+ .join(' • ');
370
198
  display += expanded;
371
199
 
372
200
  // Build response with optional hints for structured flows