slashvibe-mcp 0.2.9 → 0.3.13

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 (205) hide show
  1. package/README.md +1 -0
  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 +154 -5
  14. package/crypto.js +3 -8
  15. package/games/arcade.js +406 -0
  16. package/games/chess.js +451 -0
  17. package/games/colorguess.js +343 -0
  18. package/games/crossword-words.js +171 -0
  19. package/games/crossword.js +461 -0
  20. package/games/drawing.js +347 -0
  21. package/games/gameroulette.js +300 -0
  22. package/games/gamerouter.js +336 -0
  23. package/games/gamestatus.js +337 -0
  24. package/games/guessnumber.js +209 -0
  25. package/games/hangman.js +279 -0
  26. package/games/memory.js +338 -0
  27. package/games/multiplayer-tictactoe.js +389 -0
  28. package/games/pixelart.js +399 -0
  29. package/games/quickduel.js +354 -0
  30. package/games/riddle.js +371 -0
  31. package/games/rockpaperscissors.js +291 -0
  32. package/games/snake.js +406 -0
  33. package/games/storybuilder.js +343 -0
  34. package/games/tictactoe.js +345 -0
  35. package/games/twentyquestions.js +286 -0
  36. package/games/twotruths.js +207 -0
  37. package/games/werewolf.js +508 -0
  38. package/games/wordassociation.js +247 -0
  39. package/games/wordchain.js +135 -0
  40. package/index.js +105 -216
  41. package/intelligence/index.js +45 -0
  42. package/intelligence/infer.js +316 -0
  43. package/intelligence/interests.js +369 -0
  44. package/intelligence/patterns.js +651 -0
  45. package/intelligence/proactive.js +358 -0
  46. package/intelligence/serendipity.js +306 -0
  47. package/memory.js +166 -0
  48. package/notification-emitter.js +77 -0
  49. package/notify.js +102 -1
  50. package/package.json +21 -7
  51. package/prompts.js +1 -1
  52. package/protocol/index.js +161 -1
  53. package/setup.js +402 -0
  54. package/store/api.js +528 -82
  55. package/store/profiles.js +160 -12
  56. package/tools/_actions.js +463 -16
  57. package/tools/_deprecated/auto-suggest-connections.js +304 -0
  58. package/tools/_deprecated/bootstrap-skills.js +231 -0
  59. package/tools/_deprecated/bridge-dashboard.js +342 -0
  60. package/tools/_deprecated/bridge-health.js +400 -0
  61. package/tools/_deprecated/bridge-live.js +384 -0
  62. package/tools/_deprecated/bridges.js +383 -0
  63. package/tools/_deprecated/colorguess.js +281 -0
  64. package/tools/_deprecated/discover-insights.js +379 -0
  65. package/tools/_deprecated/discover-momentum.js +256 -0
  66. package/tools/_deprecated/discovery-analytics.js +345 -0
  67. package/tools/_deprecated/discovery-auto-suggest.js +275 -0
  68. package/tools/_deprecated/discovery-bootstrap.js +267 -0
  69. package/tools/_deprecated/discovery-daily.js +375 -0
  70. package/tools/_deprecated/discovery-dashboard.js +385 -0
  71. package/tools/_deprecated/discovery-digest.js +314 -0
  72. package/tools/_deprecated/discovery-hub.js +357 -0
  73. package/tools/_deprecated/discovery-insights.js +384 -0
  74. package/tools/_deprecated/discovery-momentum.js +281 -0
  75. package/tools/_deprecated/discovery-monitor.js +319 -0
  76. package/tools/_deprecated/discovery-proactive.js +300 -0
  77. package/tools/_deprecated/draw.js +317 -0
  78. package/tools/_deprecated/farcaster.js +307 -0
  79. package/tools/_deprecated/forget.js +119 -0
  80. package/tools/_deprecated/games-catalog.js +376 -0
  81. package/tools/_deprecated/games.js +313 -0
  82. package/tools/_deprecated/guessnumber.js +194 -0
  83. package/tools/_deprecated/hangman.js +129 -0
  84. package/tools/_deprecated/multiplayer-tictactoe.js +303 -0
  85. package/tools/_deprecated/recall.js +147 -0
  86. package/tools/_deprecated/remember.js +86 -0
  87. package/tools/_deprecated/riddle.js +240 -0
  88. package/tools/_deprecated/run-bootstrap.js +69 -0
  89. package/tools/_deprecated/skills-analytics.js +349 -0
  90. package/tools/_deprecated/skills-bootstrap.js +301 -0
  91. package/tools/_deprecated/skills-dashboard.js +268 -0
  92. package/tools/_deprecated/skills.js +380 -0
  93. package/tools/_deprecated/smart-intro.js +353 -0
  94. package/tools/_deprecated/storybuilder.js +331 -0
  95. package/tools/_deprecated/telegram-bot.js +183 -0
  96. package/tools/_deprecated/telegram-setup.js +214 -0
  97. package/tools/_deprecated/twentyquestions.js +143 -0
  98. package/tools/_discovery-enhanced.js +290 -0
  99. package/tools/_discovery.js +439 -0
  100. package/tools/_proactive-discovery.js +301 -0
  101. package/tools/_shared/index.js +64 -0
  102. package/tools/_shared.js +234 -0
  103. package/tools/_work-context.js +338 -0
  104. package/tools/_work-context.manual-test.js +199 -0
  105. package/tools/_work-context.test.js +260 -0
  106. package/tools/activity.js +220 -0
  107. package/tools/admin-inbox.js +218 -0
  108. package/tools/agent-treasury.js +288 -0
  109. package/tools/analytics.js +191 -0
  110. package/tools/approve.js +197 -0
  111. package/tools/arcade.js +173 -0
  112. package/tools/artifact-create.js +17 -11
  113. package/tools/artifact-view.js +31 -1
  114. package/tools/artifacts-price.js +47 -43
  115. package/tools/ask-expert.js +160 -0
  116. package/tools/available.js +120 -0
  117. package/tools/become-expert.js +150 -0
  118. package/tools/broadcast.js +286 -0
  119. package/tools/chat.js +202 -0
  120. package/tools/collaborative-drawing.js +286 -0
  121. package/tools/connection-status.js +178 -0
  122. package/tools/crossword.js +17 -1
  123. package/tools/discover.js +350 -34
  124. package/tools/dm.js +115 -73
  125. package/tools/drawing.js +1 -1
  126. package/tools/earnings.js +126 -0
  127. package/tools/echo.js +16 -0
  128. package/tools/feed.js +51 -7
  129. package/tools/follow.js +224 -0
  130. package/tools/friends.js +207 -0
  131. package/tools/game.js +2 -2
  132. package/tools/genesis.js +233 -0
  133. package/tools/gig-browse.js +206 -0
  134. package/tools/gig-complete.js +144 -0
  135. package/tools/handoff.js +7 -1
  136. package/tools/help.js +4 -4
  137. package/tools/idea.js +9 -2
  138. package/tools/inbox.js +334 -28
  139. package/tools/init.js +727 -36
  140. package/tools/invite.js +15 -4
  141. package/tools/l2-bridge.js +272 -0
  142. package/tools/l2-status.js +217 -0
  143. package/tools/l2.js +206 -0
  144. package/tools/migrate.js +156 -0
  145. package/tools/mint.js +377 -0
  146. package/tools/multiplayer-game.js +1 -1
  147. package/tools/notifications.js +415 -0
  148. package/tools/observe.js +200 -0
  149. package/tools/onboarding.js +147 -0
  150. package/tools/open.js +143 -12
  151. package/tools/party-game.js +2 -2
  152. package/tools/plan.js +225 -0
  153. package/tools/presence-agent.js +7 -0
  154. package/tools/proof-of-work.js +100 -104
  155. package/tools/pulse.js +218 -0
  156. package/tools/reply.js +166 -0
  157. package/tools/report.js +2 -2
  158. package/tools/reputation.js +175 -0
  159. package/tools/request.js +17 -3
  160. package/tools/schedule.js +367 -0
  161. package/tools/search-messages.js +123 -0
  162. package/tools/session.js +420 -0
  163. package/tools/session_price.js +128 -0
  164. package/tools/settings.js +126 -3
  165. package/tools/ship.js +31 -8
  166. package/tools/shipback.js +326 -0
  167. package/tools/smart-check.js +201 -0
  168. package/tools/social-processor.js +445 -0
  169. package/tools/solo-game.js +1 -1
  170. package/tools/start.js +335 -93
  171. package/tools/status.js +53 -6
  172. package/tools/stuck.js +297 -0
  173. package/tools/subscribe.js +148 -0
  174. package/tools/subscriptions.js +134 -0
  175. package/tools/suggest-tags.js +6 -8
  176. package/tools/tag-suggestions.js +257 -0
  177. package/tools/tip.js +193 -0
  178. package/tools/token.js +103 -0
  179. package/tools/update.js +1 -1
  180. package/tools/wallet.js +239 -186
  181. package/tools/watch.js +157 -0
  182. package/tools/webhook-test.js +388 -0
  183. package/tools/who.js +54 -3
  184. package/tools/withdraw.js +145 -0
  185. package/tools/work-summary.js +96 -0
  186. package/tools/workshop.js +327 -0
  187. package/tools/x-mentions.js +1 -1
  188. package/version.json +14 -3
  189. package/tools/artifacts-buy.js +0 -111
  190. package/tools/connect.js +0 -284
  191. package/tools/gigs-apply.js +0 -99
  192. package/tools/gigs-browse.js +0 -114
  193. package/tools/gigs-complete.js +0 -128
  194. package/tools/gigs-post.js +0 -140
  195. package/tools/live-off.js +0 -109
  196. package/tools/live-watch.js +0 -176
  197. package/tools/live.js +0 -128
  198. package/tools/sessions-browse.js +0 -105
  199. package/tools/whats-happening.js +0 -125
  200. /package/tools/{away.js → _deprecated/away.js} +0 -0
  201. /package/tools/{back.js → _deprecated/back.js} +0 -0
  202. /package/tools/{mute.js → _deprecated/mute.js} +0 -0
  203. /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
  204. /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
  205. /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
@@ -1,148 +1,144 @@
1
1
  /**
2
- * vibe proof-of-work — View your complete proof-of-work profile
2
+ * vibe_proof_of_work - Get unified proof-of-work summary for a user
3
3
  *
4
- * Shows your unified proof-of-work across PAST, PRESENT, and FUTURE:
5
- * - PAST: Artifacts created/sold, sessions shared, revenue
6
- * - PRESENT: What you're working on, live status
7
- * - FUTURE: Gigs completed, hire availability, reputation
8
- *
9
- * Part of /vibe's native monetization layer.
4
+ * Shows the complete "proof of work" picture:
5
+ * - PAST: Artifacts created/sold, sessions shared
6
+ * - PRESENT: Live presence, current work
7
+ * - FUTURE: Gigs completed/posted, hire availability
8
+ * - UNIFIED: Vibe score, tier, proven skills
10
9
  */
11
10
 
12
11
  const config = require('../config');
13
- const { requireInit } = require('./_shared');
12
+ const { requireInit, normalizeHandle, displayHandle, header, divider, emptyState, formatTimeAgo } = require('./_shared');
13
+ const { actions, formatActions } = require('./_actions');
14
14
 
15
- const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
15
+ const BASE_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
16
16
 
17
17
  const definition = {
18
18
  name: 'vibe_proof_of_work',
19
- description: 'View your complete proof-of-work profile: artifacts sold, gigs completed, skills proven, reputation tier. Search triggers: "proof of work", "my profile", "my reputation", "my stats", "show my work".',
19
+ description: 'Get proof-of-work summary for a user. Shows artifacts, sessions, gigs, vibe score, and proven skills.',
20
20
  inputSchema: {
21
21
  type: 'object',
22
22
  properties: {
23
23
  handle: {
24
24
  type: 'string',
25
- description: 'Handle to view (defaults to your own). Must not include @.'
25
+ description: 'User handle to look up (default: yourself)'
26
26
  }
27
27
  }
28
28
  }
29
29
  };
30
30
 
31
- // Tier emoji mapping
32
- const TIER_EMOJI = {
33
- newcomer: '🌱',
34
- rising: 'šŸ“ˆ',
35
- proven: 'āœ…',
36
- established: '⭐',
37
- expert: 'šŸ†'
38
- };
39
-
40
31
  async function handler(args) {
41
32
  const initCheck = requireInit();
42
33
  if (initCheck) return initCheck;
43
34
 
44
- const handle = args.handle || config.getHandle();
45
- const normalizedHandle = handle.replace('@', '').toLowerCase();
46
- const isOwnProfile = normalizedHandle === config.getHandle();
35
+ const targetHandle = normalizeHandle(args.handle) || config.getHandle();
47
36
 
48
37
  try {
49
- const response = await fetch(`${API_URL}/api/proof-of-work?handle=${encodeURIComponent(normalizedHandle)}`);
38
+ const response = await fetch(`${BASE_URL}/api/proof-of-work?handle=${encodeURIComponent(targetHandle)}`);
50
39
  const data = await response.json();
51
40
 
52
41
  if (!data.success) {
53
- return {
54
- display: `āŒ Could not load proof-of-work: ${data.error || 'User not found'}`
55
- };
56
- }
57
-
58
- const tierEmoji = TIER_EMOJI[data.tier] || '🌱';
59
-
60
- let display = `## ${tierEmoji} Proof of Work: @${data.handle}\n\n`;
61
-
62
- // Overall score and tier
63
- display += `**Score:** ${data.proof_of_work_score} points\n`;
64
- display += `**Tier:** ${data.tier.charAt(0).toUpperCase() + data.tier.slice(1)}\n\n`;
65
-
66
- // PAST
67
- display += `### šŸ“š PAST (Artifacts & Sessions)\n`;
68
- display += `- Artifacts created: ${data.artifacts.created}\n`;
69
- display += `- For sale: ${data.artifacts.for_sale}\n`;
70
- display += `- Sold: ${data.artifacts.sold}\n`;
71
- if (data.artifacts.revenue_cents > 0) {
72
- display += `- Revenue: $${(data.artifacts.revenue_cents / 100).toFixed(2)}\n`;
42
+ return { display: `āŒ ${data.error || 'Failed to fetch proof-of-work'}` };
73
43
  }
74
- display += `- Sessions shared: ${data.sessions.shared}\n\n`;
75
44
 
76
- // PRESENT
77
- display += `### šŸ”“ PRESENT (Live Status)\n`;
78
- display += `- Status: ${data.presence.status}\n`;
79
- if (data.presence.working_on) {
80
- display += `- Working on: ${data.presence.working_on}\n`;
45
+ const pow = data.proof_of_work;
46
+ const isSelf = targetHandle === config.getHandle();
47
+
48
+ // Build display
49
+ let display = header(`Proof of Work: ${displayHandle(targetHandle)}`, 2) + '\n\n';
50
+
51
+ // Vibe Score & Tier
52
+ display += `**Vibe Score:** ${pow.unified?.vibe_score || 0}\n`;
53
+ display += `**Tier:** ${formatTier(pow.unified?.tier)}\n\n`;
54
+
55
+ // ━━━ PAST ━━━
56
+ display += header('šŸ“œ PAST (Artifacts & Sessions)', 3) + '\n';
57
+ const past = pow.past || {};
58
+ display += `• **Artifacts Created:** ${past.artifacts_created || 0}\n`;
59
+ display += `• **Artifacts Sold:** ${past.artifacts_sold || 0}\n`;
60
+ display += `• **Revenue Earned:** $${((past.revenue_cents || 0) / 100).toFixed(2)}\n`;
61
+ display += `• **Sessions Shared:** ${past.sessions_shared || 0}\n\n`;
62
+
63
+ // ━━━ PRESENT ━━━
64
+ display += header('🟢 PRESENT (Live)', 3) + '\n';
65
+ const present = pow.present || {};
66
+ if (present.is_online) {
67
+ display += `• **Status:** Online\n`;
68
+ if (present.working_on) {
69
+ display += `• **Working on:** ${present.working_on}\n`;
70
+ }
71
+ if (present.project) {
72
+ display += `• **Project:** ${present.project}\n`;
73
+ }
74
+ } else {
75
+ display += `• **Status:** Offline\n`;
76
+ if (present.last_seen) {
77
+ display += `• **Last seen:** ${formatTimeAgo(present.last_seen)}\n`;
78
+ }
81
79
  }
82
- if (data.presence.project) {
83
- display += `- Project: ${data.presence.project}\n`;
80
+ display += '\n';
81
+
82
+ // ━━━ FUTURE ━━━
83
+ display += header('šŸš€ FUTURE (Gigs & Availability)', 3) + '\n';
84
+ const future = pow.future || {};
85
+ display += `• **Gigs Completed:** ${future.gigs_completed || 0}\n`;
86
+ display += `• **Gigs Posted:** ${future.gigs_posted || 0}\n`;
87
+ display += `• **Avg Rating:** ${future.avg_rating ? `${future.avg_rating.toFixed(1)}⭐` : 'No ratings yet'}\n`;
88
+ display += `• **Available for Hire:** ${future.available_for_hire ? 'Yes' : 'No'}\n\n`;
89
+
90
+ // ━━━ PROVEN SKILLS ━━━
91
+ const skills = pow.unified?.proven_skills || [];
92
+ if (skills.length > 0) {
93
+ display += header('šŸ› ļø Proven Skills', 3) + '\n';
94
+ skills.slice(0, 10).forEach(skill => {
95
+ display += `• ${skill.name} (${skill.count} gig${skill.count !== 1 ? 's' : ''})\n`;
96
+ });
97
+ display += '\n';
84
98
  }
85
- display += `\n`;
86
99
 
87
- // FUTURE
88
- display += `### šŸš€ FUTURE (Gigs & Hire)\n`;
89
- display += `- Gigs completed: ${data.gigs.completed}\n`;
90
- if (data.gigs.in_progress > 0) {
91
- display += `- Gigs in progress: ${data.gigs.in_progress}\n`;
92
- }
93
- if (data.gigs.rating_avg) {
94
- display += `- Average rating: ${data.gigs.rating_avg}/5\n`;
100
+ // ━━━ SCORE BREAKDOWN ━━━
101
+ const breakdown = pow.unified?.score_breakdown;
102
+ if (breakdown) {
103
+ display += header('šŸ“Š Score Breakdown', 3) + '\n';
104
+ display += `• Gigs: ${breakdown.gigs || 0}\n`;
105
+ display += `• Artifacts: ${breakdown.artifacts || 0}\n`;
106
+ display += `• Sessions: ${breakdown.sessions || 0}\n`;
107
+ display += `• Invites: ${breakdown.invites || 0}\n`;
108
+ display += `• Ships: ${breakdown.ships || 0}\n`;
95
109
  }
96
- display += `- Available for hire: ${data.hire.available ? 'Yes' : 'No'}\n`;
97
- if (data.hire.rate) {
98
- display += `- Hourly rate: $${data.hire.rate}\n`;
99
- }
100
- if (data.hire.skills && data.hire.skills.length > 0) {
101
- display += `- Skills: ${data.hire.skills.join(', ')}\n`;
102
- }
103
- display += `\n`;
104
110
 
105
- // Proven skills
106
- const skills = Object.entries(data.proven_skills || {});
107
- if (skills.length > 0) {
108
- display += `### šŸ› ļø Proven Skills\n`;
109
- skills.sort((a, b) => b[1] - a[1]);
110
- skills.slice(0, 5).forEach(([skill, level]) => {
111
- const bars = 'ā–ˆ'.repeat(Math.min(level, 5)) + 'ā–‘'.repeat(5 - Math.min(level, 5));
112
- display += `- ${skill}: ${bars} (${level})\n`;
113
- });
114
- display += `\n`;
111
+ // CTA for self
112
+ if (isSelf) {
113
+ display += divider();
114
+ display += 'šŸ’” **Boost your score:** Complete gigs (+50), sell artifacts (+30), share sessions (+10)\n';
115
115
  }
116
116
 
117
- // Call to action
118
- display += `---\n`;
119
- if (isOwnProfile) {
120
- if (data.artifacts.for_sale === 0) {
121
- display += `šŸ’” **Tip:** Price your artifacts to earn from your work!\n`;
122
- }
123
- if (data.gigs.completed === 0) {
124
- display += `šŸ’” **Tip:** Complete gigs to boost your reputation and tier.\n`;
125
- }
126
- } else {
127
- if (data.hire.available) {
128
- display += `šŸ“© DM @${data.handle} to discuss a project\n`;
129
- }
130
- }
117
+ // Build result with tip actions
118
+ const result = { display };
119
+ result.actions = formatActions(actions.afterProofOfWork(targetHandle, isSelf));
131
120
 
132
- return {
133
- display,
134
- data: {
135
- handle: data.handle,
136
- score: data.proof_of_work_score,
137
- tier: data.tier
138
- }
139
- };
121
+ return result;
140
122
 
141
123
  } catch (e) {
142
- return {
143
- display: `āŒ Error loading proof-of-work: ${e.message}`
144
- };
124
+ console.error('[proof-of-work] Error:', e);
125
+ return { display: `āŒ Failed to fetch proof-of-work: ${e.message}` };
145
126
  }
146
127
  }
147
128
 
129
+ /**
130
+ * Format tier with emoji
131
+ */
132
+ function formatTier(tier) {
133
+ const tiers = {
134
+ newcomer: '🌱 Newcomer',
135
+ rising: 'šŸ“ˆ Rising',
136
+ proven: 'āœ… Proven',
137
+ established: 'šŸ† Established',
138
+ expert: '⭐ Expert',
139
+ legendary: 'šŸ‘‘ Legendary'
140
+ };
141
+ return tiers[tier] || tier || 'Unknown';
142
+ }
143
+
148
144
  module.exports = { definition, handler };
package/tools/pulse.js ADDED
@@ -0,0 +1,218 @@
1
+ /**
2
+ * vibe pulse — Lightweight presence check for inline display
3
+ *
4
+ * Returns a compact "mini card" showing who's online.
5
+ * Designed to be called periodically by Claude based on CLAUDE.md instructions.
6
+ *
7
+ * Unlike vibe_who (full presence), this is:
8
+ * - Faster (minimal data)
9
+ * - Compact (fits in a response footer)
10
+ * - Suggestive (gentle nudges to connect)
11
+ */
12
+
13
+ const config = require('../config');
14
+ const store = require('../store');
15
+
16
+ const definition = {
17
+ name: 'vibe_pulse',
18
+ description: 'Quick presence check for inline display. Returns compact mini card showing who\'s online. Call periodically (~every 5 messages) to maintain ambient awareness.',
19
+ inputSchema: {
20
+ type: 'object',
21
+ properties: {}
22
+ }
23
+ };
24
+
25
+ // Gentle nudge templates
26
+ const NUDGES = {
27
+ just_joined: [
28
+ "{handle} just joined — say hi?",
29
+ "{handle} is here! Quick wave?",
30
+ "New face: {handle} just arrived"
31
+ ],
32
+ shipping: [
33
+ "{handle} is shipping — cheer them on?",
34
+ "{handle} in flow mode šŸ”„"
35
+ ],
36
+ debugging: [
37
+ "{handle} debugging — maybe you can help?",
38
+ "{handle} stuck on something"
39
+ ],
40
+ active: [
41
+ "{handle} is around",
42
+ "{handle} building right now"
43
+ ],
44
+ multiple: [
45
+ "{count} builders online — you're not alone",
46
+ "{count} people vibing right now",
47
+ "Room's alive: {count} online"
48
+ ]
49
+ };
50
+
51
+ function pickRandom(arr) {
52
+ return arr[Math.floor(Math.random() * arr.length)];
53
+ }
54
+
55
+ function formatNudge(template, vars) {
56
+ let result = template;
57
+ for (const [key, value] of Object.entries(vars)) {
58
+ result = result.replace(`{${key}}`, value);
59
+ }
60
+ return result;
61
+ }
62
+
63
+ function getStatusEmoji(user) {
64
+ // Explicit mood takes priority
65
+ if (user.mood === 'šŸ”„' || user.mood === 'shipping') return 'šŸ”„';
66
+ if (user.mood === 'šŸ›' || user.mood === 'debugging') return 'šŸ›';
67
+ if (user.mood === '🧠' || user.mood === 'thinking') return '🧠';
68
+ if (user.mood === 'šŸŽ‰' || user.mood === 'celebrating') return 'šŸŽ‰';
69
+ if (user.mood === 'šŸŽ§') return 'šŸŽ§';
70
+ if (user.mood === 'ā˜•') return 'ā˜•';
71
+
72
+ // Infer from what they're building (one_liner)
73
+ const workingOn = (user.one_liner || '').toLowerCase();
74
+ if (workingOn.includes('ai') || workingOn.includes('agent') || workingOn.includes('llm')) return 'šŸ¤–';
75
+ if (workingOn.includes('creative') || workingOn.includes('art') || workingOn.includes('design')) return 'šŸŽØ';
76
+ if (workingOn.includes('game') || workingOn.includes('chess') || workingOn.includes('play')) return 'šŸŽ®';
77
+ if (workingOn.includes('social') || workingOn.includes('chat') || workingOn.includes('message')) return 'šŸ’¬';
78
+ if (workingOn.includes('crypto') || workingOn.includes('web3') || workingOn.includes('token')) return 'ā›“ļø';
79
+ if (workingOn.includes('tool') || workingOn.includes('platform') || workingOn.includes('infra')) return 'šŸ› ļø';
80
+ if (workingOn.includes('mobile') || workingOn.includes('app')) return 'šŸ“±';
81
+ if (workingOn.includes('data') || workingOn.includes('analytics')) return 'šŸ“Š';
82
+
83
+ // Infer from builderMode
84
+ if (user.builderMode === 'deep-focus') return '🧘';
85
+ if (user.builderMode === 'shipping') return 'šŸš€';
86
+ if (user.builderMode === 'exploring') return 'šŸ”';
87
+ if (user.builderMode === 'focused') return 'šŸ’»';
88
+
89
+ // Fallback to recency
90
+ const age = Date.now() - user.lastSeen;
91
+ if (age < 2 * 60 * 1000) return '⚔'; // Active < 2min
92
+ if (age < 5 * 60 * 1000) return 'ā—'; // Recent < 5min
93
+ return 'ā—‹'; // Idle
94
+ }
95
+
96
+ function getUserState(user) {
97
+ const age = Date.now() - user.lastSeen;
98
+
99
+ // Check for just joined (< 2 min session)
100
+ if (user.firstSeen) {
101
+ const sessionDuration = (user.lastSeen - new Date(user.firstSeen).getTime()) / 60000;
102
+ if (sessionDuration < 2 && age < 2 * 60 * 1000) {
103
+ return 'just_joined';
104
+ }
105
+ }
106
+
107
+ if (user.mood === 'šŸ”„' || user.mood === 'shipping') return 'shipping';
108
+ if (user.mood === 'šŸ›' || user.mood === 'debugging') return 'debugging';
109
+
110
+ return 'active';
111
+ }
112
+
113
+ async function handler(args) {
114
+ // Check if initialized
115
+ if (!config.isInitialized()) {
116
+ return {
117
+ display: null,
118
+ empty: true,
119
+ reason: 'not_initialized'
120
+ };
121
+ }
122
+
123
+ const myHandle = config.getHandle();
124
+
125
+ // Fetch presence and unread (wrap in Promise.resolve for local store compat)
126
+ const [users, unreadCount] = await Promise.all([
127
+ Promise.resolve(store.getActiveUsers()).catch(() => []),
128
+ Promise.resolve(store.getUnreadCount(myHandle)).catch(() => 0)
129
+ ]);
130
+
131
+ const others = users.filter(u => u.handle !== myHandle);
132
+
133
+ // Empty room
134
+ if (others.length === 0 && unreadCount === 0) {
135
+ return {
136
+ display: null,
137
+ empty: true,
138
+ reason: 'no_one_online'
139
+ };
140
+ }
141
+
142
+ // Smart truncate: break at word boundary, add ellipsis if truncated
143
+ function smartTruncate(text, maxLen) {
144
+ if (!text || text.length <= maxLen) return text || '';
145
+ // Find last space before maxLen
146
+ const truncated = text.slice(0, maxLen);
147
+ const lastSpace = truncated.lastIndexOf(' ');
148
+ if (lastSpace > maxLen * 0.6) {
149
+ // Break at word if we're not losing too much
150
+ return truncated.slice(0, lastSpace) + '…';
151
+ }
152
+ return truncated.slice(0, maxLen - 1) + '…';
153
+ }
154
+
155
+ // Get short status label from what they're working on
156
+ function getStatusLabel(user) {
157
+ if (user.mood === 'šŸ”„' || user.mood === 'shipping') return 'shipping';
158
+ if (user.mood === 'šŸ›' || user.mood === 'debugging') return 'debugging';
159
+ if (user.mood === '🧠' || user.mood === 'thinking') return 'thinking';
160
+ if (user.mood === 'šŸŽ‰' || user.mood === 'celebrating') return 'shipped!';
161
+ if (user.mood === 'šŸ‘„' || user.mood === 'pairing') return 'pairing';
162
+ if (user.mood === 'šŸŽ§') return 'vibing';
163
+ if (user.note) return smartTruncate(user.note, 28);
164
+ if (user.one_liner) return smartTruncate(user.one_liner, 28);
165
+ return '';
166
+ }
167
+
168
+ // Build compact single-line summary optimized for collapsed view
169
+ // This ONE line must contain all key info since details get hidden
170
+ const topUsers = others.slice(0, 4);
171
+ const userChips = topUsers.map(u => `${getStatusEmoji(u)} ${u.handle}`).join(' Ā· ');
172
+ const moreCount = others.length > 4 ? ` +${others.length - 4}` : '';
173
+ const dmsBadge = unreadCount > 0 ? `šŸ“¬ ${unreadCount} Ā· ` : '';
174
+
175
+ // Single summary line with all key info
176
+ let display = `${dmsBadge}ā˜• ${others.length} online — ${userChips}${moreCount}`;
177
+
178
+ // Generate nudge
179
+ let nudge = null;
180
+ if (others.length > 0) {
181
+ // Find most interesting user for nudge
182
+ const states = others.map(u => ({ user: u, state: getUserState(u) }));
183
+
184
+ // Priority: just_joined > shipping > debugging > active
185
+ const priority = ['just_joined', 'shipping', 'debugging', 'active'];
186
+ let picked = null;
187
+
188
+ for (const state of priority) {
189
+ picked = states.find(s => s.state === state);
190
+ if (picked) break;
191
+ }
192
+
193
+ if (picked) {
194
+ const templates = NUDGES[picked.state] || NUDGES.active;
195
+ nudge = formatNudge(pickRandom(templates), {
196
+ handle: `@${picked.user.handle}`,
197
+ count: others.length
198
+ });
199
+ } else if (others.length >= 2) {
200
+ nudge = formatNudge(pickRandom(NUDGES.multiple), { count: others.length });
201
+ }
202
+ }
203
+
204
+ // Add nudge after details
205
+ if (nudge) {
206
+ display += `\n\n_${nudge}_`;
207
+ }
208
+
209
+ return {
210
+ display,
211
+ online_count: others.length,
212
+ unread_count: unreadCount,
213
+ nudge,
214
+ handles: others.slice(0, 5).map(u => u.handle)
215
+ };
216
+ }
217
+
218
+ module.exports = { definition, handler };
package/tools/reply.js ADDED
@@ -0,0 +1,166 @@
1
+ /**
2
+ * vibe reply — Quick reply to the most recent unread message
3
+ *
4
+ * Streamlined flow: one command instead of inbox → open → dm
5
+ */
6
+
7
+ const config = require('../config');
8
+ const store = require('../store');
9
+ const patterns = require('../intelligence/patterns');
10
+ const userProfiles = require('../store/profiles');
11
+ const { trackMessage } = require('./summarize');
12
+ const { requireInit, normalizeHandle, truncate, warning, fetchRelevantUsers } = require('./_shared');
13
+ const { actions, formatActions } = require('./_actions');
14
+
15
+ const definition = {
16
+ name: 'vibe_reply',
17
+ description: 'Quick reply to your most recent unread message, or to a specific person. Streamlined: one command instead of inbox → open → dm.',
18
+ inputSchema: {
19
+ type: 'object',
20
+ properties: {
21
+ message: {
22
+ type: 'string',
23
+ description: 'Your reply message'
24
+ },
25
+ to: {
26
+ type: 'string',
27
+ description: 'Optional: reply to a specific person (e.g., @alex). If not provided, replies to most recent unread.'
28
+ }
29
+ },
30
+ required: ['message']
31
+ }
32
+ };
33
+
34
+ async function handler(args) {
35
+ const initCheck = requireInit();
36
+ if (initCheck) return initCheck;
37
+
38
+ const { message, to } = args;
39
+ const myHandle = config.getHandle();
40
+
41
+ if (!message || message.trim().length === 0) {
42
+ return { display: 'Need a message to reply with.' };
43
+ }
44
+
45
+ let targetHandle;
46
+ let threadContext = null;
47
+
48
+ // If specific recipient provided, use that
49
+ if (to) {
50
+ targetHandle = normalizeHandle(to);
51
+ } else {
52
+ // Find most recent unread thread
53
+ const threads = await store.getInbox(myHandle);
54
+
55
+ if (!threads || threads.length === 0) {
56
+ return {
57
+ display: 'šŸ“­ No messages to reply to.\n\nUse `vibe dm @someone "message"` to start a conversation.',
58
+ actions: formatActions(actions.recommendedConnections([]))
59
+ };
60
+ }
61
+
62
+ // Find first thread with unread messages
63
+ const unreadThread = threads.find(t => t.unread > 0);
64
+
65
+ if (!unreadThread) {
66
+ // No unread, show most recent thread
67
+ const mostRecent = threads[0];
68
+ return {
69
+ display: `šŸ“­ All caught up! Most recent: @${mostRecent.handle}\n\nUse \`vibe reply "message" --to @${mostRecent.handle}\` to continue that conversation.`,
70
+ actions: formatActions(actions.afterInboxCompact([{ handle: mostRecent.handle, unread: 0 }]))
71
+ };
72
+ }
73
+
74
+ targetHandle = unreadThread.handle;
75
+ threadContext = {
76
+ unreadCount: unreadThread.unread,
77
+ preview: unreadThread.lastMessage ? truncate(unreadThread.lastMessage, 50) : null
78
+ };
79
+ }
80
+
81
+ // Can't reply to yourself
82
+ if (targetHandle === myHandle) {
83
+ return { display: 'You can\'t DM yourself.' };
84
+ }
85
+
86
+ // Route @echo messages to the echo agent
87
+ if (targetHandle === 'echo') {
88
+ const echo = require('./echo');
89
+ return echo.handler({ message, anonymous: false });
90
+ }
91
+
92
+ // Truncate message if too long
93
+ const MAX_LENGTH = 2000;
94
+ const trimmed = message.trim();
95
+ const wasTruncated = trimmed.length > MAX_LENGTH;
96
+ const finalMessage = wasTruncated ? trimmed.substring(0, MAX_LENGTH) : trimmed;
97
+
98
+ // Send typing indicator (shows "typing..." to recipient)
99
+ store.sendTypingIndicator(myHandle, targetHandle).catch(() => {});
100
+
101
+ // Send the message
102
+ const result = await store.sendMessage(myHandle, targetHandle, finalMessage, 'dm', null);
103
+
104
+ // Check for errors
105
+ if (result && result.error) {
106
+ return {
107
+ display: `āŒ **Failed to send reply**\n\n${result.message}\n\n_Please try again._`
108
+ };
109
+ }
110
+
111
+ // Mark the thread as read since we're replying
112
+ try {
113
+ await store.markThreadRead(myHandle, targetHandle);
114
+ } catch (e) {
115
+ // Non-fatal - continue
116
+ console.warn('[reply] Failed to mark thread as read:', e.message);
117
+ }
118
+
119
+ // Log social pattern
120
+ patterns.logMessageSent(targetHandle);
121
+
122
+ // Record connection if first time
123
+ try {
124
+ const hasConnected = await userProfiles.hasBeenConnected(myHandle, targetHandle);
125
+ if (!hasConnected) {
126
+ await userProfiles.recordConnection(myHandle, targetHandle, 'first_message');
127
+ }
128
+ } catch (e) {
129
+ console.warn('[reply] Failed to update profile connection:', e);
130
+ }
131
+
132
+ // Track for session summary
133
+ trackMessage(myHandle, targetHandle, 'sent');
134
+
135
+ // Build response
136
+ let display = `āœ“ Replied to **@${targetHandle}**`;
137
+
138
+ if (wasTruncated) {
139
+ display += ` ${warning(`truncated to ${MAX_LENGTH} chars`)}`;
140
+ }
141
+
142
+ if (threadContext) {
143
+ display += `\n\n_${threadContext.unreadCount} message${threadContext.unreadCount > 1 ? 's' : ''} marked as read_`;
144
+ }
145
+
146
+ // Check for more unread
147
+ const remainingThreads = await store.getInbox(myHandle);
148
+ const stillUnread = remainingThreads.filter(t => t.unread > 0 && t.handle !== targetHandle);
149
+
150
+ if (stillUnread.length > 0) {
151
+ const nextHandle = stillUnread[0].handle;
152
+ const totalUnread = stillUnread.reduce((sum, t) => sum + t.unread, 0);
153
+ display += `\n\nšŸ“¬ ${totalUnread} more unread from ${stillUnread.map(t => `@${t.handle}`).slice(0, 3).join(', ')}`;
154
+ display += `\n_Say \`vibe reply "message"\` to reply to @${nextHandle}_`;
155
+ }
156
+
157
+ // Build response with actions
158
+ const response = { display };
159
+
160
+ // Suggest follow-up actions
161
+ response.actions = formatActions(actions.afterDm(targetHandle));
162
+
163
+ return response;
164
+ }
165
+
166
+ module.exports = { definition, handler };
package/tools/report.js CHANGED
@@ -83,11 +83,11 @@ Nice try, but you can't report yourself.`
83
83
 
84
84
  ${result.error || 'Could not submit report. Please try again.'}
85
85
 
86
- If the problem persists, DM @seth directly.`
86
+ If the problem persists, DM @vibe for help.`
87
87
  };
88
88
  }
89
89
 
90
- let display = `## Report Submitted
90
+ const display = `## Report Submitted
91
91
 
92
92
  **Report ID:** ${result.report_id}
93
93
  **Reported:** @${reportedHandle}