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.
Files changed (235) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +280 -47
  3. package/auto-update.js +10 -15
  4. package/config.js +36 -31
  5. package/crypto.js +1 -6
  6. package/debug.js +12 -0
  7. package/discord.js +19 -19
  8. package/eslint.config.js +54 -0
  9. package/index.js +217 -207
  10. package/intelligence/index.js +2 -9
  11. package/intelligence/infer.js +10 -16
  12. package/intelligence/patterns.js +23 -18
  13. package/intelligence/proactive.js +16 -15
  14. package/intelligence/serendipity.js +57 -20
  15. package/memory.js +13 -8
  16. package/migrate-v2.js +72 -0
  17. package/notification-emitter.js +2 -2
  18. package/notify.js +39 -14
  19. package/package.json +28 -29
  20. package/post-install.js +141 -0
  21. package/presence.js +2 -2
  22. package/prompts.js +5 -9
  23. package/protocol/index.js +123 -87
  24. package/protocol/telegram-commands.js +36 -37
  25. package/store/api.js +358 -529
  26. package/store/local.js +9 -10
  27. package/store/profiles.js +48 -192
  28. package/store/reservations.js +2 -9
  29. package/store/skills.js +69 -71
  30. package/store/sqlite.js +355 -0
  31. package/test-skills-bootstrap.js +20 -0
  32. package/test-v2-integration.js +385 -0
  33. package/tools/_actions.js +48 -387
  34. package/tools/_connection-queue.js +45 -56
  35. package/tools/_discovery-enhanced.js +52 -57
  36. package/tools/_discovery.js +87 -185
  37. package/tools/{l2-status.js → _experimental/l2-status.js} +68 -70
  38. package/tools/{shipback.js → _experimental/shipback.js} +4 -3
  39. package/tools/_proactive-discovery.js +60 -73
  40. package/tools/_shared/index.js +41 -64
  41. package/tools/admin-inbox.js +10 -15
  42. package/tools/agents.js +1 -1
  43. package/tools/artifact-create.js +13 -23
  44. package/tools/artifact-view.js +4 -4
  45. package/tools/{_deprecated/back.js → back.js} +1 -1
  46. package/tools/bye.js +3 -5
  47. package/tools/consent.js +2 -2
  48. package/tools/context.js +9 -10
  49. package/tools/crossword.js +3 -2
  50. package/tools/discover.js +94 -356
  51. package/tools/dm.js +27 -86
  52. package/tools/doctor.js +12 -41
  53. package/tools/drawing.js +34 -20
  54. package/tools/echo.js +11 -11
  55. package/tools/feed.js +30 -58
  56. package/tools/follow.js +64 -187
  57. package/tools/{_deprecated/forget.js → forget.js} +4 -7
  58. package/tools/game.js +144 -48
  59. package/tools/handoff.js +6 -8
  60. package/tools/help.js +3 -3
  61. package/tools/idea.js +15 -27
  62. package/tools/inbox.js +121 -293
  63. package/tools/init.js +54 -151
  64. package/tools/invite.js +8 -21
  65. package/tools/migrate.js +27 -24
  66. package/tools/multiplayer-game.js +50 -40
  67. package/tools/{_deprecated/mute.js → mute.js} +4 -3
  68. package/tools/notifications.js +58 -48
  69. package/tools/observe.js +12 -15
  70. package/tools/onboarding.js +8 -11
  71. package/tools/open.js +13 -144
  72. package/tools/party-game.js +23 -12
  73. package/tools/patterns.js +2 -1
  74. package/tools/ping.js +5 -7
  75. package/tools/react.js +28 -30
  76. package/tools/{_deprecated/recall.js → recall.js} +5 -10
  77. package/tools/release.js +4 -2
  78. package/tools/{_deprecated/remember.js → remember.js} +4 -6
  79. package/tools/report.js +2 -2
  80. package/tools/request.js +6 -26
  81. package/tools/reserve.js +1 -1
  82. package/tools/session-fork.js +97 -0
  83. package/tools/session-save.js +109 -0
  84. package/tools/settings.js +30 -99
  85. package/tools/ship.js +74 -56
  86. package/tools/{_deprecated/skills-exchange.js → skills-exchange.js} +38 -39
  87. package/tools/social-inbox.js +22 -28
  88. package/tools/social-post.js +24 -27
  89. package/tools/solo-game.js +54 -46
  90. package/tools/start.js +14 -148
  91. package/tools/status.js +21 -68
  92. package/tools/submit.js +4 -2
  93. package/tools/suggest-tags.js +36 -33
  94. package/tools/summarize.js +19 -16
  95. package/tools/tag-suggestions.js +72 -73
  96. package/tools/test.js +1 -1
  97. package/tools/{_deprecated/tictactoe.js → tictactoe.js} +26 -26
  98. package/tools/token.js +4 -4
  99. package/tools/update.js +1 -2
  100. package/tools/watch.js +132 -112
  101. package/tools/who.js +20 -40
  102. package/tools/{_deprecated/wordassociation.js → wordassociation.js} +23 -20
  103. package/tools/workshop-buddy.js +52 -53
  104. package/tools/x-mentions.js +0 -1
  105. package/tools/x-reply.js +0 -1
  106. package/twitter.js +14 -20
  107. package/version.json +8 -10
  108. package/webhook-runner.js +132 -0
  109. package/auth-store.js +0 -148
  110. package/bridges/bridge-monitor.js +0 -388
  111. package/bridges/discord-bot.js +0 -431
  112. package/bridges/farcaster.js +0 -299
  113. package/bridges/telegram.js +0 -261
  114. package/bridges/webhook-health.js +0 -420
  115. package/bridges/webhook-server.js +0 -437
  116. package/bridges/whatsapp.js +0 -441
  117. package/bridges/x-webhook.js +0 -423
  118. package/games/arcade.js +0 -406
  119. package/games/chess.js +0 -451
  120. package/games/colorguess.js +0 -343
  121. package/games/crossword-words.js +0 -171
  122. package/games/crossword.js +0 -461
  123. package/games/drawing.js +0 -347
  124. package/games/gameroulette.js +0 -300
  125. package/games/gamerouter.js +0 -336
  126. package/games/gamestatus.js +0 -337
  127. package/games/guessnumber.js +0 -209
  128. package/games/hangman.js +0 -279
  129. package/games/memory.js +0 -338
  130. package/games/multiplayer-tictactoe.js +0 -389
  131. package/games/pixelart.js +0 -399
  132. package/games/quickduel.js +0 -354
  133. package/games/riddle.js +0 -371
  134. package/games/rockpaperscissors.js +0 -291
  135. package/games/snake.js +0 -406
  136. package/games/storybuilder.js +0 -343
  137. package/games/tictactoe.js +0 -345
  138. package/games/twentyquestions.js +0 -286
  139. package/games/twotruths.js +0 -207
  140. package/games/werewolf.js +0 -508
  141. package/games/wordassociation.js +0 -247
  142. package/games/wordchain.js +0 -135
  143. package/intelligence/interests.js +0 -369
  144. package/setup.js +0 -480
  145. package/smart-inbox.js +0 -276
  146. package/tools/_deprecated/auto-suggest-connections.js +0 -304
  147. package/tools/_deprecated/bootstrap-skills.js +0 -231
  148. package/tools/_deprecated/bridge-dashboard.js +0 -342
  149. package/tools/_deprecated/bridge-health.js +0 -400
  150. package/tools/_deprecated/bridge-live.js +0 -384
  151. package/tools/_deprecated/bridges.js +0 -383
  152. package/tools/_deprecated/colorguess.js +0 -281
  153. package/tools/_deprecated/discover-insights.js +0 -379
  154. package/tools/_deprecated/discover-momentum.js +0 -256
  155. package/tools/_deprecated/discovery-analytics.js +0 -345
  156. package/tools/_deprecated/discovery-auto-suggest.js +0 -275
  157. package/tools/_deprecated/discovery-bootstrap.js +0 -267
  158. package/tools/_deprecated/discovery-daily.js +0 -375
  159. package/tools/_deprecated/discovery-dashboard.js +0 -385
  160. package/tools/_deprecated/discovery-digest.js +0 -314
  161. package/tools/_deprecated/discovery-hub.js +0 -357
  162. package/tools/_deprecated/discovery-insights.js +0 -384
  163. package/tools/_deprecated/discovery-momentum.js +0 -281
  164. package/tools/_deprecated/discovery-monitor.js +0 -319
  165. package/tools/_deprecated/discovery-proactive.js +0 -300
  166. package/tools/_deprecated/draw.js +0 -317
  167. package/tools/_deprecated/farcaster.js +0 -307
  168. package/tools/_deprecated/games-catalog.js +0 -376
  169. package/tools/_deprecated/games.js +0 -313
  170. package/tools/_deprecated/guessnumber.js +0 -194
  171. package/tools/_deprecated/hangman.js +0 -129
  172. package/tools/_deprecated/multiplayer-tictactoe.js +0 -303
  173. package/tools/_deprecated/riddle.js +0 -240
  174. package/tools/_deprecated/run-bootstrap.js +0 -69
  175. package/tools/_deprecated/skills-analytics.js +0 -349
  176. package/tools/_deprecated/skills-bootstrap.js +0 -301
  177. package/tools/_deprecated/skills-dashboard.js +0 -268
  178. package/tools/_deprecated/skills.js +0 -380
  179. package/tools/_deprecated/smart-intro.js +0 -353
  180. package/tools/_deprecated/storybuilder.js +0 -331
  181. package/tools/_deprecated/telegram-bot.js +0 -183
  182. package/tools/_deprecated/telegram-setup.js +0 -214
  183. package/tools/_deprecated/twentyquestions.js +0 -143
  184. package/tools/_shared.js +0 -234
  185. package/tools/_work-context.js +0 -338
  186. package/tools/_work-context.manual-test.js +0 -199
  187. package/tools/_work-context.test.js +0 -260
  188. package/tools/activity.js +0 -220
  189. package/tools/agent-treasury.js +0 -288
  190. package/tools/analytics.js +0 -191
  191. package/tools/approve.js +0 -197
  192. package/tools/arcade.js +0 -173
  193. package/tools/artifacts-price.js +0 -107
  194. package/tools/ask-expert.js +0 -160
  195. package/tools/available.js +0 -120
  196. package/tools/become-expert.js +0 -150
  197. package/tools/broadcast.js +0 -325
  198. package/tools/chat.js +0 -202
  199. package/tools/collaborative-drawing.js +0 -286
  200. package/tools/connection-status.js +0 -178
  201. package/tools/earnings.js +0 -126
  202. package/tools/friends.js +0 -207
  203. package/tools/genesis.js +0 -233
  204. package/tools/gig-browse.js +0 -206
  205. package/tools/gig-complete.js +0 -144
  206. package/tools/health.js +0 -87
  207. package/tools/leaderboard.js +0 -117
  208. package/tools/lib/git-apply.js +0 -206
  209. package/tools/lib/git-bundle.js +0 -407
  210. package/tools/mint.js +0 -377
  211. package/tools/plan.js +0 -225
  212. package/tools/profile.js +0 -219
  213. package/tools/proof-of-work.js +0 -144
  214. package/tools/pulse.js +0 -218
  215. package/tools/reply.js +0 -166
  216. package/tools/reputation.js +0 -175
  217. package/tools/schedule.js +0 -367
  218. package/tools/search-messages.js +0 -123
  219. package/tools/session.js +0 -467
  220. package/tools/session_price.js +0 -128
  221. package/tools/smart-check.js +0 -201
  222. package/tools/social-processor.js +0 -445
  223. package/tools/streak.js +0 -147
  224. package/tools/stuck.js +0 -297
  225. package/tools/subscribe.js +0 -148
  226. package/tools/subscriptions.js +0 -134
  227. package/tools/tip.js +0 -193
  228. package/tools/wallet.js +0 -269
  229. package/tools/webhook-test.js +0 -388
  230. package/tools/withdraw.js +0 -145
  231. package/tools/work-summary.js +0 -96
  232. package/tools/workshop.js +0 -327
  233. /package/tools/{l2-bridge.js → _experimental/l2-bridge.js} +0 -0
  234. /package/tools/{l2.js → _experimental/l2.js} +0 -0
  235. /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