slashvibe-mcp 0.3.20 → 0.3.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +47 -252
  2. package/analytics.js +107 -0
  3. package/auth-store.js +148 -0
  4. package/auto-update.js +130 -0
  5. package/bridges/bridge-monitor.js +388 -0
  6. package/bridges/discord-bot.js +431 -0
  7. package/bridges/farcaster.js +299 -0
  8. package/bridges/telegram.js +261 -0
  9. package/bridges/webhook-health.js +420 -0
  10. package/bridges/webhook-server.js +437 -0
  11. package/bridges/whatsapp.js +441 -0
  12. package/bridges/x-webhook.js +423 -0
  13. package/config.js +27 -15
  14. package/games/arcade.js +406 -0
  15. package/games/chess.js +451 -0
  16. package/games/colorguess.js +343 -0
  17. package/games/crossword-words.js +171 -0
  18. package/games/crossword.js +461 -0
  19. package/games/drawing.js +347 -0
  20. package/games/gameroulette.js +300 -0
  21. package/games/gamerouter.js +336 -0
  22. package/games/gamestatus.js +337 -0
  23. package/games/guessnumber.js +209 -0
  24. package/games/hangman.js +279 -0
  25. package/games/memory.js +338 -0
  26. package/games/multiplayer-tictactoe.js +389 -0
  27. package/games/pixelart.js +399 -0
  28. package/games/quickduel.js +354 -0
  29. package/games/riddle.js +371 -0
  30. package/games/rockpaperscissors.js +291 -0
  31. package/games/snake.js +406 -0
  32. package/games/storybuilder.js +343 -0
  33. package/games/tictactoe.js +345 -0
  34. package/games/twentyquestions.js +286 -0
  35. package/games/twotruths.js +207 -0
  36. package/games/werewolf.js +508 -0
  37. package/games/wordassociation.js +247 -0
  38. package/games/wordchain.js +135 -0
  39. package/index.js +116 -159
  40. package/intelligence/index.js +9 -2
  41. package/intelligence/interests.js +369 -0
  42. package/notification-emitter.js +77 -0
  43. package/notify.js +5 -1
  44. package/package.json +21 -16
  45. package/prompts.js +1 -1
  46. package/protocol/index.js +73 -0
  47. package/setup.js +480 -0
  48. package/smart-inbox.js +276 -0
  49. package/store/api.js +536 -215
  50. package/store/profiles.js +160 -12
  51. package/tools/_actions.js +362 -21
  52. package/tools/_discovery.js +119 -26
  53. package/tools/_shared/index.js +64 -0
  54. package/tools/_shared.js +234 -0
  55. package/tools/_work-context.js +338 -0
  56. package/tools/_work-context.manual-test.js +199 -0
  57. package/tools/_work-context.test.js +260 -0
  58. package/tools/activity.js +220 -0
  59. package/tools/analytics.js +191 -0
  60. package/tools/approve.js +197 -0
  61. package/tools/artifact-create.js +14 -3
  62. package/tools/artifacts-price.js +107 -0
  63. package/tools/available.js +120 -0
  64. package/tools/broadcast.js +325 -0
  65. package/tools/chat.js +202 -0
  66. package/tools/collaborative-drawing.js +1 -1
  67. package/tools/connection-status.js +178 -0
  68. package/tools/discover.js +350 -34
  69. package/tools/dm.js +80 -8
  70. package/tools/earnings.js +126 -0
  71. package/tools/feed.js +35 -4
  72. package/tools/follow.js +224 -0
  73. package/tools/friends.js +207 -0
  74. package/tools/gig-browse.js +206 -0
  75. package/tools/gig-complete.js +144 -0
  76. package/tools/health.js +87 -0
  77. package/tools/help.js +3 -3
  78. package/tools/idea.js +9 -2
  79. package/tools/inbox.js +289 -105
  80. package/tools/init.js +131 -34
  81. package/tools/invite.js +15 -4
  82. package/tools/leaderboard.js +117 -0
  83. package/tools/lib/git-apply.js +206 -0
  84. package/tools/lib/git-bundle.js +407 -0
  85. package/tools/migrate.js +3 -3
  86. package/tools/multiplayer-game.js +1 -1
  87. package/tools/onboarding.js +7 -7
  88. package/tools/open.js +143 -12
  89. package/tools/party-game.js +1 -1
  90. package/tools/plan.js +225 -0
  91. package/tools/proof-of-work.js +144 -0
  92. package/tools/reply.js +166 -0
  93. package/tools/report.js +1 -1
  94. package/tools/request.js +17 -3
  95. package/tools/schedule.js +367 -0
  96. package/tools/search-messages.js +123 -0
  97. package/tools/session.js +467 -0
  98. package/tools/session_price.js +128 -0
  99. package/tools/settings.js +90 -2
  100. package/tools/ship.js +30 -7
  101. package/tools/smart-check.js +201 -0
  102. package/tools/start.js +147 -12
  103. package/tools/status.js +53 -6
  104. package/tools/streak.js +147 -0
  105. package/tools/stuck.js +297 -0
  106. package/tools/subscribe.js +148 -0
  107. package/tools/subscriptions.js +134 -0
  108. package/tools/suggest-tags.js +6 -8
  109. package/tools/tag-suggestions.js +1 -1
  110. package/tools/tip.js +150 -77
  111. package/tools/token.js +4 -4
  112. package/tools/update.js +1 -1
  113. package/tools/wallet.js +221 -79
  114. package/tools/watch.js +157 -0
  115. package/tools/who.js +30 -1
  116. package/tools/withdraw.js +145 -0
  117. package/tools/work-summary.js +96 -0
  118. package/version.json +10 -8
  119. package/LICENSE +0 -21
  120. package/store/sqlite.js +0 -347
  121. /package/tools/{auto-suggest-connections.js → _deprecated/auto-suggest-connections.js} +0 -0
  122. /package/tools/{away.js → _deprecated/away.js} +0 -0
  123. /package/tools/{back.js → _deprecated/back.js} +0 -0
  124. /package/tools/{bootstrap-skills.js → _deprecated/bootstrap-skills.js} +0 -0
  125. /package/tools/{bridge-dashboard.js → _deprecated/bridge-dashboard.js} +0 -0
  126. /package/tools/{bridge-health.js → _deprecated/bridge-health.js} +0 -0
  127. /package/tools/{bridge-live.js → _deprecated/bridge-live.js} +0 -0
  128. /package/tools/{bridges.js → _deprecated/bridges.js} +0 -0
  129. /package/tools/{colorguess.js → _deprecated/colorguess.js} +0 -0
  130. /package/tools/{discover-insights.js → _deprecated/discover-insights.js} +0 -0
  131. /package/tools/{discover-momentum.js → _deprecated/discover-momentum.js} +0 -0
  132. /package/tools/{discovery-analytics.js → _deprecated/discovery-analytics.js} +0 -0
  133. /package/tools/{discovery-auto-suggest.js → _deprecated/discovery-auto-suggest.js} +0 -0
  134. /package/tools/{discovery-bootstrap.js → _deprecated/discovery-bootstrap.js} +0 -0
  135. /package/tools/{discovery-daily.js → _deprecated/discovery-daily.js} +0 -0
  136. /package/tools/{discovery-dashboard.js → _deprecated/discovery-dashboard.js} +0 -0
  137. /package/tools/{discovery-digest.js → _deprecated/discovery-digest.js} +0 -0
  138. /package/tools/{discovery-hub.js → _deprecated/discovery-hub.js} +0 -0
  139. /package/tools/{discovery-insights.js → _deprecated/discovery-insights.js} +0 -0
  140. /package/tools/{discovery-momentum.js → _deprecated/discovery-momentum.js} +0 -0
  141. /package/tools/{discovery-monitor.js → _deprecated/discovery-monitor.js} +0 -0
  142. /package/tools/{discovery-proactive.js → _deprecated/discovery-proactive.js} +0 -0
  143. /package/tools/{draw.js → _deprecated/draw.js} +0 -0
  144. /package/tools/{farcaster.js → _deprecated/farcaster.js} +0 -0
  145. /package/tools/{forget.js → _deprecated/forget.js} +0 -0
  146. /package/tools/{games-catalog.js → _deprecated/games-catalog.js} +0 -0
  147. /package/tools/{games.js → _deprecated/games.js} +0 -0
  148. /package/tools/{guessnumber.js → _deprecated/guessnumber.js} +0 -0
  149. /package/tools/{hangman.js → _deprecated/hangman.js} +0 -0
  150. /package/tools/{multiplayer-tictactoe.js → _deprecated/multiplayer-tictactoe.js} +0 -0
  151. /package/tools/{mute.js → _deprecated/mute.js} +0 -0
  152. /package/tools/{recall.js → _deprecated/recall.js} +0 -0
  153. /package/tools/{remember.js → _deprecated/remember.js} +0 -0
  154. /package/tools/{riddle.js → _deprecated/riddle.js} +0 -0
  155. /package/tools/{run-bootstrap.js → _deprecated/run-bootstrap.js} +0 -0
  156. /package/tools/{skills-analytics.js → _deprecated/skills-analytics.js} +0 -0
  157. /package/tools/{skills-bootstrap.js → _deprecated/skills-bootstrap.js} +0 -0
  158. /package/tools/{skills-dashboard.js → _deprecated/skills-dashboard.js} +0 -0
  159. /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
  160. /package/tools/{skills.js → _deprecated/skills.js} +0 -0
  161. /package/tools/{smart-intro.js → _deprecated/smart-intro.js} +0 -0
  162. /package/tools/{storybuilder.js → _deprecated/storybuilder.js} +0 -0
  163. /package/tools/{telegram-bot.js → _deprecated/telegram-bot.js} +0 -0
  164. /package/tools/{telegram-setup.js → _deprecated/telegram-setup.js} +0 -0
  165. /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
  166. /package/tools/{twentyquestions.js → _deprecated/twentyquestions.js} +0 -0
  167. /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
package/tools/plan.js ADDED
@@ -0,0 +1,225 @@
1
+ /**
2
+ * vibe_plan — Create a plan and request approval
3
+ *
4
+ * Plans are structured proposals that can be sent for async approval.
5
+ * They're artifacts with an approval workflow built in.
6
+ */
7
+
8
+ const config = require('../config');
9
+ const store = require('../store');
10
+ const { requireInit, normalizeHandle } = require('./_shared');
11
+
12
+ const definition = {
13
+ name: 'vibe_plan',
14
+ description: 'Create a plan and request approval from collaborators. Plans are structured proposals with summary, problem, changes, and impact.',
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: {
18
+ title: {
19
+ type: 'string',
20
+ description: 'Plan title (e.g., "Refactor auth system", "CLAUDE.md cleanup")'
21
+ },
22
+ summary: {
23
+ type: 'string',
24
+ description: 'One-line summary of what this plan does'
25
+ },
26
+ problem: {
27
+ type: 'string',
28
+ description: 'What problem does this solve? (optional)'
29
+ },
30
+ changes: {
31
+ type: 'array',
32
+ items: { type: 'string' },
33
+ description: 'List of proposed changes'
34
+ },
35
+ impact: {
36
+ type: 'string',
37
+ description: 'What will be affected by this change? (optional)'
38
+ },
39
+ request_approval_from: {
40
+ type: 'array',
41
+ items: { type: 'string' },
42
+ description: 'Handles to request approval from (e.g., ["@brightseth", "@alice"])'
43
+ },
44
+ requires: {
45
+ type: 'string',
46
+ enum: ['any', 'all'],
47
+ description: 'Require approval from any one person (default) or all of them'
48
+ }
49
+ },
50
+ required: ['title', 'summary', 'changes', 'request_approval_from']
51
+ }
52
+ };
53
+
54
+ // Generate artifact ID and slug
55
+ function generateArtifactId() {
56
+ return `plan_${Date.now()}_${Math.random().toString(36).substring(7)}`;
57
+ }
58
+
59
+ function generateSlug(title) {
60
+ return title
61
+ .toLowerCase()
62
+ .replace(/[^a-z0-9]+/g, '-')
63
+ .replace(/^-|-$/g, '')
64
+ .substring(0, 60);
65
+ }
66
+
67
+ async function handler(args) {
68
+ const initCheck = requireInit();
69
+ if (initCheck) return initCheck;
70
+
71
+ const {
72
+ title,
73
+ summary,
74
+ problem,
75
+ changes,
76
+ impact,
77
+ request_approval_from,
78
+ requires = 'any'
79
+ } = args;
80
+
81
+ const creator = config.getHandle();
82
+
83
+ // Validate approvers
84
+ if (!request_approval_from || request_approval_from.length === 0) {
85
+ return {
86
+ display: '❌ Must specify at least one approver in request_approval_from'
87
+ };
88
+ }
89
+
90
+ // Normalize approver handles
91
+ const approvers = request_approval_from.map(h => normalizeHandle(h));
92
+
93
+ // Build content blocks for the plan
94
+ const blocks = [
95
+ { type: 'summary', data: { text: summary } }
96
+ ];
97
+
98
+ if (problem) {
99
+ blocks.push({ type: 'problem', data: { text: problem } });
100
+ }
101
+
102
+ blocks.push({
103
+ type: 'proposed_changes',
104
+ data: { items: changes }
105
+ });
106
+
107
+ if (impact) {
108
+ blocks.push({ type: 'impact', data: { text: impact } });
109
+ }
110
+
111
+ // Build the plan artifact
112
+ const planId = generateArtifactId();
113
+ const slug = generateSlug(title);
114
+
115
+ // Build audience list (creator + all approvers)
116
+ const audience = new Set([creator, ...approvers]);
117
+
118
+ const plan = {
119
+ id: planId,
120
+ slug,
121
+ title,
122
+ template: 'plan',
123
+ content: { blocks },
124
+
125
+ // Social metadata
126
+ created_by: creator,
127
+ created_for: approvers[0], // Primary approver
128
+ thread_id: null,
129
+
130
+ // Privacy - plans are unlisted by default
131
+ visibility: 'unlisted',
132
+ audience: Array.from(audience),
133
+
134
+ // Provenance
135
+ provenance: {
136
+ source_type: 'manual',
137
+ personalized_for: null,
138
+ notes: null
139
+ },
140
+
141
+ // Lifecycle
142
+ created_at: new Date().toISOString(),
143
+ updated_at: new Date().toISOString(),
144
+ expires_at: null,
145
+
146
+ // Evolution
147
+ revision: 1,
148
+ forked_from: null,
149
+
150
+ // Approval workflow
151
+ approval: {
152
+ status: 'pending',
153
+ requested_from: approvers,
154
+ requires,
155
+ responses: [],
156
+ resolved_at: null
157
+ }
158
+ };
159
+
160
+ // Store the plan
161
+ const storeResult = await store.createArtifact(plan);
162
+
163
+ if (!storeResult.success) {
164
+ return {
165
+ display: `❌ Failed to create plan: ${storeResult.error}`
166
+ };
167
+ }
168
+
169
+ const planUrl = `https://slashvibe.dev/a/${slug}`;
170
+
171
+ // Send DM to each approver
172
+ const dmResults = [];
173
+ for (const approver of approvers) {
174
+ try {
175
+ const dmResult = await store.sendMessage(creator, approver,
176
+ `📋 **Plan: ${title}**\n\n` +
177
+ `**Summary:** ${summary}\n\n` +
178
+ (problem ? `**Problem:** ${problem}\n\n` : '') +
179
+ `**Changes:**\n${changes.map(c => `• ${c}`).join('\n')}\n\n` +
180
+ (impact ? `**Impact:** ${impact}\n\n` : '') +
181
+ `---\n` +
182
+ `👍 Approve · 🔄 Request changes · ❌ Reject\n` +
183
+ `Use: \`vibe approve ${slug}\` or \`vibe approve ${slug} --reject\``,
184
+ {
185
+ type: 'plan_card',
186
+ plan_id: planId,
187
+ plan_slug: slug,
188
+ plan_url: planUrl
189
+ }
190
+ );
191
+ dmResults.push({ approver, success: dmResult.success });
192
+ } catch (error) {
193
+ console.error(`[PLAN] Failed to DM ${approver}:`, error);
194
+ dmResults.push({ approver, success: false, error: error.message });
195
+ }
196
+ }
197
+
198
+ // Build response
199
+ const successfulDMs = dmResults.filter(r => r.success).length;
200
+ const approverList = approvers.map(a => `@${a}`).join(', ');
201
+
202
+ let display = `📋 **Plan created: ${title}**\n\n`;
203
+ display += `**Status:** ⏳ Pending approval\n`;
204
+ display += `**Requested from:** ${approverList}\n`;
205
+ display += `**Requires:** ${requires === 'all' ? 'All approvers' : 'Any approver'}\n\n`;
206
+
207
+ if (successfulDMs === approvers.length) {
208
+ display += `✓ Sent to ${successfulDMs} approver${successfulDMs > 1 ? 's' : ''}\n`;
209
+ } else {
210
+ display += `⚠️ Sent to ${successfulDMs}/${approvers.length} approvers\n`;
211
+ }
212
+
213
+ display += `\n**Plan ID:** \`${slug}\``;
214
+
215
+ return {
216
+ display,
217
+ plan_id: planId,
218
+ plan_slug: slug,
219
+ plan_url: planUrl,
220
+ approval_status: 'pending',
221
+ approvers
222
+ };
223
+ }
224
+
225
+ module.exports = { definition, handler };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * vibe_proof_of_work - Get unified proof-of-work summary for a user
3
+ *
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
9
+ */
10
+
11
+ const config = require('../config');
12
+ const { requireInit, normalizeHandle, displayHandle, header, divider, emptyState, formatTimeAgo } = require('./_shared');
13
+ const { actions, formatActions } = require('./_actions');
14
+
15
+ const BASE_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
16
+
17
+ const definition = {
18
+ name: 'vibe_proof_of_work',
19
+ description: 'Get proof-of-work summary for a user. Shows artifacts, sessions, gigs, vibe score, and proven skills.',
20
+ inputSchema: {
21
+ type: 'object',
22
+ properties: {
23
+ handle: {
24
+ type: 'string',
25
+ description: 'User handle to look up (default: yourself)'
26
+ }
27
+ }
28
+ }
29
+ };
30
+
31
+ async function handler(args) {
32
+ const initCheck = requireInit();
33
+ if (initCheck) return initCheck;
34
+
35
+ const targetHandle = normalizeHandle(args.handle) || config.getHandle();
36
+
37
+ try {
38
+ const response = await fetch(`${BASE_URL}/api/proof-of-work?handle=${encodeURIComponent(targetHandle)}`);
39
+ const data = await response.json();
40
+
41
+ if (!data.success) {
42
+ return { display: `❌ ${data.error || 'Failed to fetch proof-of-work'}` };
43
+ }
44
+
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
+ }
79
+ }
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';
98
+ }
99
+
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`;
109
+ }
110
+
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
+ }
116
+
117
+ // Build result with tip actions
118
+ const result = { display };
119
+ result.actions = formatActions(actions.afterProofOfWork(targetHandle, isSelf));
120
+
121
+ return result;
122
+
123
+ } catch (e) {
124
+ console.error('[proof-of-work] Error:', e);
125
+ return { display: `❌ Failed to fetch proof-of-work: ${e.message}` };
126
+ }
127
+ }
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
+
144
+ 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
@@ -87,7 +87,7 @@ 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}
package/tools/request.js CHANGED
@@ -64,9 +64,16 @@ async function handler(args) {
64
64
  if (args.claim) {
65
65
  // Post a claim as a special entry
66
66
  try {
67
+ // Include auth token for API authorization
68
+ const authToken = config.getAuthToken();
69
+ const headers = { 'Content-Type': 'application/json' };
70
+ if (authToken) {
71
+ headers['Authorization'] = `Bearer ${authToken}`;
72
+ }
73
+
67
74
  const response = await fetch(`${apiUrl}/api/board`, {
68
75
  method: 'POST',
69
- headers: { 'Content-Type': 'application/json' },
76
+ headers,
70
77
  body: JSON.stringify({
71
78
  author: myHandle,
72
79
  category: 'claim',
@@ -103,9 +110,16 @@ async function handler(args) {
103
110
  const fullContent = `${content}${bountyText}`;
104
111
 
105
112
  try {
113
+ // Include auth token for API authorization
114
+ const authToken = config.getAuthToken();
115
+ const headers = { 'Content-Type': 'application/json' };
116
+ if (authToken) {
117
+ headers['Authorization'] = `Bearer ${authToken}`;
118
+ }
119
+
106
120
  const response = await fetch(`${apiUrl}/api/board`, {
107
121
  method: 'POST',
108
- headers: { 'Content-Type': 'application/json' },
122
+ headers,
109
123
  body: JSON.stringify({
110
124
  author: myHandle,
111
125
  category: 'request',
@@ -136,7 +150,7 @@ async function handler(args) {
136
150
  // Browse requests
137
151
  try {
138
152
  const limit = Math.min(args.limit || 10, 30);
139
- let url = `${apiUrl}/api/board?limit=${limit * 2}&category=request`;
153
+ const url = `${apiUrl}/api/board?limit=${limit * 2}&category=request`;
140
154
 
141
155
  // Also get claims to show status
142
156
  const claimsUrl = `${apiUrl}/api/board?limit=50&category=claim`;