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
package/tools/dm.js CHANGED
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * vibe dm — Send a direct message
3
- * Supports internal /vibe DMs and external platforms (Gmail, X, Farcaster, etc.)
4
3
  */
5
4
 
6
5
  const config = require('../config');
@@ -9,13 +8,12 @@ const memory = require('../memory');
9
8
  const userProfiles = require('../store/profiles');
10
9
  const patterns = require('../intelligence/patterns');
11
10
  const { trackMessage, checkBurst } = require('./summarize');
12
- const { requireInit, normalizeHandle, truncate, warning } = require('./_shared');
11
+ const { requireInit, normalizeHandle, truncate, warning, fetchRelevantUsers } = require('./_shared');
13
12
  const { actions, formatActions } = require('./_actions');
14
- const router = require('../../lib/messaging/router');
15
13
 
16
14
  const definition = {
17
15
  name: 'vibe_dm',
18
- description: 'Send a direct message to someone. Can include structured payload for games, handoffs, etc.',
16
+ description: 'Send a direct message to someone. Can include structured payload for games, handoffs, artifact cards, or an instant USDC tip.',
19
17
  inputSchema: {
20
18
  type: 'object',
21
19
  properties: {
@@ -27,9 +25,21 @@ const definition = {
27
25
  type: 'string',
28
26
  description: 'Your message'
29
27
  },
28
+ artifact_slug: {
29
+ type: 'string',
30
+ description: 'Optional artifact slug to share (e.g., "pizza-guide-abc123"). The artifact will be shown as a rich card.'
31
+ },
30
32
  payload: {
31
33
  type: 'object',
32
34
  description: 'Optional structured data (game state, code review, handoff, etc.)'
35
+ },
36
+ reply_to: {
37
+ type: 'string',
38
+ description: 'Optional: Message ID to reply to (creates a threaded reply)'
39
+ },
40
+ tip_amount_cents: {
41
+ type: 'number',
42
+ description: 'Optional: Attach an instant USDC tip (100 = $1, 500 = $5, 1000 = $10)'
33
43
  }
34
44
  },
35
45
  required: ['handle']
@@ -40,7 +50,7 @@ async function handler(args) {
40
50
  const initCheck = requireInit();
41
51
  if (initCheck) return initCheck;
42
52
 
43
- const { handle, message, payload } = args;
53
+ const { handle, message, artifact_slug, payload, reply_to, tip_amount_cents } = args;
44
54
  const myHandle = config.getHandle();
45
55
  const them = normalizeHandle(handle);
46
56
 
@@ -54,82 +64,51 @@ async function handler(args) {
54
64
  return { display: 'You can\'t DM yourself.' };
55
65
  }
56
66
 
57
- // Need either message or payload
58
- if ((!message || message.trim().length === 0) && !payload) {
59
- return { display: 'Need either a message or payload.' };
60
- }
61
-
62
- // Check if this is an external platform message (email, X, Farcaster, etc.)
63
- const platform = router.detectPlatform(handle);
64
-
65
- if (platform !== 'vibe') {
66
- // External platform - route via messaging adapters
67
+ // Handle artifact sharing
68
+ let finalPayload = payload;
69
+ if (artifact_slug) {
67
70
  try {
68
- const result = await router.send(handle, message, {
69
- handle: myHandle,
70
- subject: args.subject, // For email
71
- platform: args.platform // Allow explicit platform override
72
- });
71
+ // Fetch artifact from API
72
+ const { getArtifactBySlug } = require('./artifact-view');
73
+ const artifact = await getArtifactBySlug(artifact_slug);
73
74
 
74
- // Auth required
75
- if (result.authRequired) {
76
- return {
77
- display: `🔐 Connect ${platform} first
78
-
79
- ${platform} is not connected yet. Connect it to send messages:
80
-
81
- vibe connect ${platform}
82
-
83
- Then you can message:
84
- vibe dm ${handle} "${message.slice(0, 50)}..."
85
- `
86
- };
87
- }
88
-
89
- // Success
90
- if (result.success) {
91
- return {
92
- display: `✓ Sent via ${platform}
93
-
94
- To: ${handle}
95
- Message: "${truncate(message, 100)}"
96
-
97
- ${platform === 'gmail' ? `Message ID: ${result.messageId}` : ''}
98
- `
99
- };
75
+ if (!artifact) {
76
+ return { display: `Artifact not found: ${artifact_slug}` };
100
77
  }
101
78
 
102
- // Error
103
- return {
104
- display: `❌ Failed to send via ${platform}
105
-
106
- Error: ${result.error}
107
-
108
- ${platform} may need to be reconnected:
109
- vibe connect ${platform} --action status
110
- `
111
- };
79
+ // Create artifact payload
80
+ const protocol = require('../protocol');
81
+ finalPayload = protocol.createArtifactPayload(artifact);
112
82
  } catch (error) {
113
- return {
114
- display: `❌ Error sending via ${platform}
115
-
116
- ${error.message}
117
-
118
- Make sure ${platform} is configured:
119
- vibe connect ${platform}
120
- `
121
- };
83
+ console.error('Failed to load artifact:', error);
84
+ return { display: `Failed to load artifact: ${error.message}` };
122
85
  }
123
86
  }
124
87
 
125
- // Continue with internal /vibe DM logic below...
88
+ // Need either message or payload
89
+ if ((!message || message.trim().length === 0) && !finalPayload) {
90
+ return { display: 'Need either a message, artifact, or payload.' };
91
+ }
126
92
 
127
93
  const trimmed = message ? message.trim() : '';
128
94
  const MAX_LENGTH = 2000;
129
95
  const wasTruncated = trimmed.length > MAX_LENGTH;
130
96
  const finalMessage = wasTruncated ? trimmed.substring(0, MAX_LENGTH) : trimmed;
131
97
 
132
- await store.sendMessage(myHandle, them, finalMessage || null, 'dm', payload);
98
+ // Send typing indicator (shows "typing..." to recipient while message is being sent)
99
+ // Non-blocking - we don't wait for this
100
+ store.sendTypingIndicator(myHandle, them).catch(() => {});
101
+
102
+ const result = await store.sendMessage(myHandle, them, finalMessage || null, 'dm', finalPayload, {
103
+ replyTo: reply_to || null,
104
+ });
105
+
106
+ // Check for errors
107
+ if (result && result.error) {
108
+ return {
109
+ display: `❌ **Failed to send message**\n\n${result.message}\n\n_Please try again. If the problem persists, check your connection._`
110
+ };
111
+ }
133
112
 
134
113
  // Log social pattern (quietly, in background)
135
114
  patterns.logMessageSent(them);
@@ -156,13 +135,56 @@ Make sure ${platform} is configured:
156
135
  display += ` ${warning(`truncated to ${MAX_LENGTH} chars`)}`;
157
136
  }
158
137
 
159
- // Show message preview or payload type
160
- if (finalMessage) {
161
- display += `\n\n"${truncate(finalMessage, 100)}"`;
138
+ // Only show payload type indicator (message already visible in tool call)
139
+ if (finalPayload) {
140
+ const payloadType = finalPayload.type || 'data';
141
+ if (payloadType === 'artifact') {
142
+ const icon = finalPayload.template === 'guide' ? '📘' : finalPayload.template === 'learning' ? '💡' : finalPayload.template === 'workspace' ? '🗂️' : '📦';
143
+ display += `\n\n${icon} _Shared artifact: ${finalPayload.title}_`;
144
+ } else {
145
+ display += `\n\n📦 _Includes ${payloadType} payload_`;
146
+ }
162
147
  }
163
- if (payload) {
164
- const payloadType = payload.type || 'data';
165
- display += `\n\n📦 _Includes ${payloadType} payload_`;
148
+
149
+ // Execute attached tip if specified
150
+ let tipResult = null;
151
+ if (tip_amount_cents && tip_amount_cents > 0) {
152
+ const token = config.getToken();
153
+ if (token) {
154
+ try {
155
+ const apiUrl = config.getApiUrl();
156
+ // Generate idempotency key to prevent duplicate tips from retries
157
+ const timeBucket = Math.floor(Date.now() / 60000);
158
+ const tipIdempotencyKey = `dm:${myHandle}:${them}:${tip_amount_cents}:${timeBucket}`;
159
+
160
+ const tipResponse = await fetch(`${apiUrl}/api/tips/instant`, {
161
+ method: 'POST',
162
+ headers: {
163
+ 'Content-Type': 'application/json',
164
+ 'Authorization': `Bearer ${token}`,
165
+ 'Idempotency-Key': tipIdempotencyKey
166
+ },
167
+ body: JSON.stringify({
168
+ to: them,
169
+ amount_cents: tip_amount_cents,
170
+ message: message ? `${message.substring(0, 50)}...` : null,
171
+ context: { type: 'dm_reply' }
172
+ })
173
+ });
174
+
175
+ tipResult = await tipResponse.json();
176
+
177
+ if (tipResult.success) {
178
+ const tipAmount = (tip_amount_cents / 100).toFixed(0);
179
+ display += `\n\n💸 _Tipped $${tipAmount} USDC — [view tx](${tipResult.explorer_url})_`;
180
+ } else {
181
+ display += `\n\n⚠️ _Tip failed: ${tipResult.message || 'Unknown error'}_`;
182
+ }
183
+ } catch (tipError) {
184
+ console.warn('[dm] Tip execution failed:', tipError.message);
185
+ display += `\n\n⚠️ _Tip failed: ${tipError.message}_`;
186
+ }
187
+ }
166
188
  }
167
189
 
168
190
  // Burst notification (5+ messages in one thread)
@@ -192,6 +214,26 @@ Make sure ${platform} is configured:
192
214
  // Add guided mode actions
193
215
  response.actions = formatActions(actions.afterDm(them));
194
216
 
217
+ // Fetch DM suggestions (async, non-blocking for response)
218
+ // This adds "You might want to message..." suggestions
219
+ try {
220
+ const suggestions = await fetchRelevantUsers(myHandle, 'dm_suggest', 3);
221
+ if (suggestions && suggestions.matches && suggestions.matches.length > 0) {
222
+ // Filter out the person we just messaged
223
+ const others = suggestions.matches.filter(m => m.handle !== them);
224
+ if (others.length > 0) {
225
+ response.dm_suggestions = others.map(m => ({
226
+ handle: m.handle,
227
+ building: m.building,
228
+ reasons: m.reasons?.slice(0, 2) || []
229
+ }));
230
+ }
231
+ }
232
+ } catch (e) {
233
+ // Don't fail DM if suggestions fail
234
+ console.log('[dm] dm_suggest fetch error:', e.message);
235
+ }
236
+
195
237
  return response;
196
238
  }
197
239
 
package/tools/drawing.js CHANGED
@@ -86,7 +86,7 @@ async function saveDrawingState(room, state) {
86
86
  * Post drawing activity to board
87
87
  */
88
88
  async function postDrawingActivity(action, room, player, details = '') {
89
- const API_URL = process.env.VIBE_API_URL || 'https://slashvibe.dev';
89
+ const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
90
90
 
91
91
  try {
92
92
  let content;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * vibe_earnings - View unified earnings dashboard
3
+ *
4
+ * Shows all revenue sources aggregated:
5
+ * - Artifact sales
6
+ * - Session PPV revenue
7
+ * - Subscription revenue
8
+ * - Gig completions
9
+ * - Tips received
10
+ * - On-chain balance
11
+ *
12
+ * Examples:
13
+ * - "vibe earnings"
14
+ * - "show my earnings"
15
+ * - "how much have I made"
16
+ */
17
+
18
+ const fetch = require('node-fetch');
19
+ const config = require('../config');
20
+
21
+ const definition = {
22
+ name: 'vibe_earnings',
23
+ description: 'View your unified earnings dashboard - artifact sales, session PPV, subscriptions, gigs, tips, and on-chain balance.',
24
+ inputSchema: {
25
+ type: 'object',
26
+ properties: {
27
+ handle: {
28
+ type: 'string',
29
+ description: 'Handle to check earnings for (defaults to your handle)'
30
+ }
31
+ }
32
+ }
33
+ };
34
+
35
+ async function handler(args) {
36
+ const { handle } = args;
37
+
38
+ if (!config.isInitialized()) {
39
+ return {
40
+ display: 'Run `vibe init` first to set your identity.'
41
+ };
42
+ }
43
+
44
+ const targetHandle = handle || config.getHandle();
45
+ const apiUrl = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
46
+ const token = config.getToken();
47
+
48
+ try {
49
+ const response = await fetch(
50
+ `${apiUrl}/api/earnings?handle=${encodeURIComponent(targetHandle)}`,
51
+ {
52
+ headers: token ? {
53
+ 'Authorization': `Bearer ${token}`
54
+ } : {}
55
+ }
56
+ );
57
+
58
+ const result = await response.json();
59
+
60
+ if (!response.ok) {
61
+ return {
62
+ display: `❌ ${result.error || 'Failed to fetch earnings'}`
63
+ };
64
+ }
65
+
66
+ const { earnings, total_earned_display, pending_display, on_chain } = result;
67
+
68
+ let formatted = `
69
+ ┌────────────────────────────────────────────────────────────┐
70
+ │ 💰 EARNINGS DASHBOARD │
71
+ │ @${targetHandle.padEnd(33)}│
72
+ ├────────────────────────────────────────────────────────────┤
73
+ │ │
74
+ │ Total Earned: ${(total_earned_display || '$0').padEnd(40)} │
75
+ │ Pending (On-chain): ${(pending_display || '$0').padEnd(34)} │
76
+ │ │
77
+ ├────────────────────────────────────────────────────────────┤
78
+ │ 📊 Breakdown by Source: │
79
+ │ │
80
+ │ Artifacts: ${(earnings.artifacts.revenue_display || '$0').padEnd(15)} (${earnings.artifacts.sales || 0} sales)${' '.repeat(17)} │
81
+ │ Sessions: ${(earnings.sessions.revenue_display || '$0').padEnd(15)} (${earnings.sessions.ppv_sales || 0} PPV)${' '.repeat(18)} │
82
+ │ Gigs: ${(earnings.gigs.revenue_display || '$0').padEnd(15)} (${earnings.gigs.completed || 0} completed)${' '.repeat(12)} │
83
+ │ Tips: ${(earnings.tips.revenue_display || '$0').padEnd(15)} (${earnings.tips.received || 0} received)${' '.repeat(13)} │
84
+ │ │
85
+ `.trim();
86
+
87
+ if (on_chain) {
88
+ formatted += `
89
+ ├────────────────────────────────────────────────────────────┤
90
+ │ ⛓️ On-Chain Status: │`;
91
+
92
+ if (on_chain.registered) {
93
+ const walletDisplay = on_chain.wallet
94
+ ? `${on_chain.wallet.slice(0, 10)}...${on_chain.wallet.slice(-8)}`
95
+ : 'not set';
96
+ formatted += `
97
+ │ │
98
+ │ Registered: Yes │
99
+ │ Wallet: ${walletDisplay.padEnd(47)} │
100
+ │ Balance: ${(on_chain.balance_eth + ' ETH').padEnd(46)} │`;
101
+ if (on_chain.can_withdraw) {
102
+ formatted += `
103
+ │ ✅ Ready to withdraw │`;
104
+ }
105
+ } else {
106
+ formatted += `
107
+ │ │
108
+ │ Registered: No │
109
+ │ ⚠️ Register to receive payments: vibe register_onchain │`;
110
+ }
111
+ }
112
+
113
+ formatted += `
114
+ │ │
115
+ └────────────────────────────────────────────────────────────┘`;
116
+
117
+ return { display: formatted, data: result };
118
+
119
+ } catch (error) {
120
+ return {
121
+ display: `❌ Failed to fetch earnings: ${error.message}`
122
+ };
123
+ }
124
+ }
125
+
126
+ module.exports = { definition, handler };
package/tools/echo.js CHANGED
@@ -12,6 +12,7 @@
12
12
 
13
13
  const config = require('../config');
14
14
  const { formatTimeAgo, requireInit } = require('./_shared');
15
+ const store = require('../store');
15
16
 
16
17
  const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
17
18
 
@@ -186,6 +187,21 @@ async function handler(args) {
186
187
  return { display };
187
188
  }
188
189
 
190
+ // Track feedback for onboarding checklist
191
+ try {
192
+ await fetch(`${API_URL}/api/onboarding/checklist`, {
193
+ method: 'POST',
194
+ headers: { 'Content-Type': 'application/json' },
195
+ body: JSON.stringify({
196
+ handle: myHandle,
197
+ taskId: 'leave_feedback',
198
+ metadata: { anonymous }
199
+ })
200
+ });
201
+ } catch (e) {
202
+ // Non-critical, continue
203
+ }
204
+
189
205
  if (anonymous) {
190
206
  display += pickRandom(responses.receivedAnon);
191
207
  } else {
package/tools/feed.js CHANGED
@@ -13,7 +13,8 @@
13
13
  */
14
14
 
15
15
  const config = require('../config');
16
- const { requireInit, header, emptyState, formatTimeAgo, divider } = require('./_shared');
16
+ const { requireInit, header, emptyState, formatTimeAgo, divider, fetchRelevantUsers } = require('./_shared');
17
+ const { actions, formatActions } = require('./_actions');
17
18
 
18
19
  const definition = {
19
20
  name: 'vibe_feed',
@@ -23,7 +24,7 @@ const definition = {
23
24
  properties: {
24
25
  filter: {
25
26
  type: 'string',
26
- enum: ['all', 'ideas', 'ships', 'requests', 'riffs'],
27
+ enum: ['all', 'ideas', 'ships', 'requests', 'riffs', 'observations'],
27
28
  description: 'Filter by type (default: all)'
28
29
  },
29
30
  from: {
@@ -48,6 +49,7 @@ const TYPE_CONFIG = {
48
49
  'shipped': { emoji: '🚀', label: 'ship', verb: 'shipped' },
49
50
  'request': { emoji: '🔓', label: 'request', verb: 'requested' },
50
51
  'claim': { emoji: '🔨', label: 'claim', verb: 'claimed' },
52
+ 'observation': { emoji: '👁️', label: 'observation', verb: 'observed' },
51
53
  'general': { emoji: '📝', label: 'post', verb: 'posted' }
52
54
  };
53
55
 
@@ -55,7 +57,8 @@ const CATEGORY_MAP = {
55
57
  'ideas': ['idea', 'riff'],
56
58
  'ships': ['shipped'],
57
59
  'requests': ['request', 'claim'],
58
- 'riffs': ['riff']
60
+ 'riffs': ['riff'],
61
+ 'observations': ['observation']
59
62
  };
60
63
 
61
64
  async function handler(args) {
@@ -98,8 +101,27 @@ async function handler(args) {
98
101
  );
99
102
  }
100
103
 
101
- // Sort by timestamp (newest first)
102
- entries.sort((a, b) => b.timestamp - a.timestamp);
104
+ // Fetch relevant users to personalize feed
105
+ // Content from relevant users gets a time boost (appears more recent)
106
+ const relevantHandles = new Set();
107
+ try {
108
+ const relevancy = await fetchRelevantUsers(myHandle, 'feed', 20);
109
+ if (relevancy && relevancy.matches) {
110
+ relevancy.matches.forEach(m => relevantHandles.add(m.handle.toLowerCase()));
111
+ }
112
+ } catch (e) {
113
+ // Don't fail feed if relevancy fails
114
+ console.log('[feed] relevancy fetch error:', e.message);
115
+ }
116
+
117
+ // Sort by timestamp with relevancy boost
118
+ // Relevant users get a 6-hour boost (their content appears "more recent")
119
+ const RELEVANCY_BOOST_MS = 6 * 60 * 60 * 1000; // 6 hours in ms
120
+ entries.sort((a, b) => {
121
+ const aBoost = relevantHandles.has(a.author.toLowerCase()) ? RELEVANCY_BOOST_MS : 0;
122
+ const bBoost = relevantHandles.has(b.author.toLowerCase()) ? RELEVANCY_BOOST_MS : 0;
123
+ return (b.timestamp + bBoost) - (a.timestamp + aBoost);
124
+ });
103
125
 
104
126
  // Limit
105
127
  entries = entries.slice(0, limit);
@@ -163,17 +185,39 @@ async function handler(args) {
163
185
  // Tags (non-system ones)
164
186
  if (entry.tags && entry.tags.length > 0) {
165
187
  const visibleTags = entry.tags
166
- .filter(t => !t.startsWith('inspired:') && !t.startsWith('fulfills:') && !t.startsWith('riff:') && !t.startsWith('claim:'))
188
+ .filter(t => !t.startsWith('inspired:') && !t.startsWith('fulfills:') && !t.startsWith('riff:') && !t.startsWith('claim:') && !t.startsWith('observation:') && !t.startsWith('type:'))
167
189
  .slice(0, 3);
168
190
  if (visibleTags.length > 0) {
169
191
  display += ` ${visibleTags.map(t => `#${t}`).join(' ')}\n`;
170
192
  }
171
193
  }
172
194
 
195
+ // Observation metadata
196
+ if (entry.category === 'observation' && entry.metadata?.observation_type) {
197
+ display += ` _type: ${entry.metadata.observation_type}_\n`;
198
+ }
199
+
200
+ // ASCII art (if present)
201
+ if (entry.metadata?.ascii_art) {
202
+ display += `\n${entry.metadata.ascii_art}\n`;
203
+ display += ` 📊 _includes visualization_\n`;
204
+ }
205
+
173
206
  display += ` _${timeAgo}_\n\n`;
174
207
  });
175
208
 
176
- return { display };
209
+ // Build response with tip actions if we have ships
210
+ const response = { display };
211
+
212
+ // Find the most recent ship to offer tip action for
213
+ const recentShip = entries.find(e => e.category === 'shipped');
214
+ if (recentShip && recentShip.author.toLowerCase() !== myHandle.toLowerCase()) {
215
+ response.actions = formatActions(actions.afterShipView(recentShip));
216
+ response.hint = 'tip_ship_available';
217
+ response.ship_author = recentShip.author;
218
+ }
219
+
220
+ return response;
177
221
 
178
222
  } catch (error) {
179
223
  return { display: `⚠️ Failed to load feed: ${error.message}` };