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
@@ -0,0 +1,147 @@
1
+ /**
2
+ * vibe onboarding โ€” View your onboarding checklist progress
3
+ *
4
+ * Shows the 5 onboarding tasks and their completion status.
5
+ * Tasks are auto-detected as you use vibe.
6
+ */
7
+
8
+ const config = require('../config');
9
+ const store = require('../store');
10
+
11
+ const definition = {
12
+ name: 'vibe_onboarding',
13
+ description: 'View your onboarding checklist progress. Shows tasks that help you get started with /vibe.',
14
+ inputSchema: {
15
+ type: 'object',
16
+ properties: {},
17
+ required: []
18
+ }
19
+ };
20
+
21
+ /**
22
+ * Format a single task for display
23
+ */
24
+ function formatTask(task, index) {
25
+ const checkbox = task.completed ? '[x]' : '[ ]';
26
+ const status = task.completed ? 'โœ“' : ' ';
27
+ const completedText = task.completed && task.completedAt
28
+ ? ` _(done ${formatTimeAgo(task.completedAt)})_`
29
+ : '';
30
+
31
+ return `${checkbox} **${task.title}**${completedText}`;
32
+ }
33
+
34
+ /**
35
+ * Format timestamp to relative time
36
+ */
37
+ function formatTimeAgo(timestamp) {
38
+ const now = Date.now();
39
+ const then = new Date(timestamp).getTime();
40
+ const seconds = Math.floor((now - then) / 1000);
41
+
42
+ if (seconds < 60) return 'just now';
43
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
44
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
45
+ return `${Math.floor(seconds / 86400)}d ago`;
46
+ }
47
+
48
+ /**
49
+ * Generate progress bar
50
+ */
51
+ function generateProgressBar(completed, total) {
52
+ const percentage = Math.round((completed / total) * 100);
53
+ const filled = Math.round((completed / total) * 10);
54
+ const empty = 10 - filled;
55
+ const bar = 'โ–ˆ'.repeat(filled) + 'โ–‘'.repeat(empty);
56
+ return `${bar} ${percentage}%`;
57
+ }
58
+
59
+ async function handler(args) {
60
+ const handle = config.getHandle();
61
+
62
+ if (!handle) {
63
+ return {
64
+ display: `## Not Initialized
65
+
66
+ Run \`vibe init\` first to set your identity and start onboarding.`
67
+ };
68
+ }
69
+
70
+ // Fetch checklist status from API
71
+ try {
72
+ const response = await store.getChecklistStatus(handle);
73
+
74
+ if (!response.success) {
75
+ return {
76
+ display: `## Onboarding Checklist
77
+
78
+ _Unable to load checklist. Try again later._
79
+
80
+ Error: ${response.error || 'Unknown error'}`
81
+ };
82
+ }
83
+
84
+ const { tasks, progress, allComplete } = response;
85
+
86
+ // Build display
87
+ let display = `## Onboarding Checklist
88
+
89
+ `;
90
+
91
+ // Progress bar
92
+ display += `**Progress:** ${generateProgressBar(progress.completed, progress.total)} (${progress.completed}/${progress.total})\n\n`;
93
+
94
+ // Celebration if complete
95
+ if (allComplete) {
96
+ display += `๐ŸŽ‰ **Congratulations!** You've completed onboarding!\n\n`;
97
+ }
98
+
99
+ // Tasks
100
+ display += `### Tasks\n\n`;
101
+ tasks.forEach((task, i) => {
102
+ display += formatTask(task, i) + '\n';
103
+ });
104
+
105
+ // Next action hints
106
+ if (!allComplete) {
107
+ display += '\n### Quick Actions\n\n';
108
+
109
+ const nextTask = tasks.find(t => !t.completed);
110
+ if (nextTask) {
111
+ switch (nextTask.id) {
112
+ case 'read_welcome':
113
+ display += 'โ†’ Say **"check my messages"** to read @seth\'s welcome\n';
114
+ break;
115
+ case 'reply_seth':
116
+ display += 'โ†’ Say **"reply to seth"** or **"dm @seth hi!"**\n';
117
+ break;
118
+ case 'find_github_friends':
119
+ display += 'โ†’ Say **"find my github friends"** to see who you know! ๐Ÿ”ฅ\n';
120
+ break;
121
+ case 'first_ship':
122
+ display += 'โ†’ Say **"ship something"** or **"I shipped X"**\n';
123
+ break;
124
+ case 'share_ship':
125
+ display += 'โ†’ After shipping, click the Twitter link to share! ๐Ÿฆ\n';
126
+ break;
127
+ case 'invite_friend':
128
+ display += 'โ†’ Say **"invite a friend"** or **"share my invite link"**\n';
129
+ break;
130
+ }
131
+ }
132
+ }
133
+
134
+ return { display };
135
+
136
+ } catch (e) {
137
+ return {
138
+ display: `## Onboarding Checklist
139
+
140
+ _Error loading checklist: ${e.message}_
141
+
142
+ Try running \`vibe test\` to check connectivity.`
143
+ };
144
+ }
145
+ }
146
+
147
+ module.exports = { definition, handler };
package/tools/open.js CHANGED
@@ -8,6 +8,15 @@ const memory = require('../memory');
8
8
  const patterns = require('../intelligence/patterns');
9
9
  const { formatPayload } = require('../protocol');
10
10
  const { requireInit, normalizeHandle } = require('./_shared');
11
+ const { actions, formatActions } = require('./_actions');
12
+
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
+ }
11
20
 
12
21
  const definition = {
13
22
  name: 'vibe_open',
@@ -46,38 +55,152 @@ async function handler(args) {
46
55
  patterns.logMessageReceived(them);
47
56
  }
48
57
 
49
- // Check if they're typing
58
+ // Check if they're typing (cross-platform: synced via KV)
50
59
  let typingNotice = '';
51
60
  try {
52
61
  const typingUsers = await store.getTypingUsers(myHandle);
53
- if (typingUsers.includes(them)) {
62
+ const isTyping = typingUsers.some(t => t.handle?.toLowerCase() === them.toLowerCase());
63
+ if (isTyping) {
54
64
  typingNotice = `\n_@${them} is typing..._\n`;
55
65
  }
56
66
  } catch (e) {}
57
67
 
68
+ // Get their presence status (for "last seen" display)
69
+ let presenceStatus = '';
70
+ try {
71
+ const activeUsers = await store.getActiveUsers();
72
+ const theirPresence = activeUsers.find(u => u.handle?.toLowerCase() === them.toLowerCase());
73
+
74
+ if (theirPresence) {
75
+ if (theirPresence.status === 'active') {
76
+ presenceStatus = '๐ŸŸข online now';
77
+ } else if (theirPresence.status === 'away') {
78
+ const ago = store.formatTimeAgo(theirPresence.lastSeen);
79
+ presenceStatus = `โ˜• away ยท last seen ${ago}`;
80
+ } else {
81
+ const ago = store.formatTimeAgo(theirPresence.lastSeen);
82
+ presenceStatus = `last seen ${ago}`;
83
+ }
84
+ }
85
+ } catch (e) {}
86
+
58
87
  if (thread.length === 0) {
59
- return {
60
- display: `## @${them}
88
+ const statusLine = presenceStatus ? `\n_${presenceStatus}_` : '';
89
+
90
+ // Fetch conversation starters to help break the ice
91
+ let starters = [];
92
+ let starterContext = {};
93
+ try {
94
+ const apiUrl = config.getApiUrl();
95
+ const promptsResponse = await fetch(`${apiUrl}/api/prompts?from=${myHandle}&to=${them}`, {
96
+ headers: { 'User-Agent': 'vibe-mcp-client' }
97
+ });
98
+ if (promptsResponse.ok) {
99
+ const data = await promptsResponse.json();
100
+ if (data.success && data.prompts) {
101
+ starters = data.prompts.slice(0, 3);
102
+ starterContext = data.context || {};
103
+ }
104
+ }
105
+ } catch (e) {}
106
+
107
+ // Build display with conversation starters
108
+ let emptyDisplay = `## @${them}${statusLine}
61
109
 
62
110
  _No messages yet._${typingNotice}
111
+ `;
112
+
113
+ if (starters.length > 0) {
114
+ emptyDisplay += `\n**๐Ÿ’ฌ Conversation starters:**\n`;
115
+ starters.forEach((starter, i) => {
116
+ emptyDisplay += `${i + 1}. "${starter}"\n`;
117
+ });
118
+ emptyDisplay += `\n_Copy one or say your own!_`;
119
+ } else {
120
+ emptyDisplay += `\nSay "message ${them} hello" to start`;
121
+ }
122
+
123
+ // Build response with actions for quick-send
124
+ const response = { display: emptyDisplay };
125
+
126
+ if (starters.length > 0) {
127
+ response.actions = formatActions(starters.map((starter, i) => ({
128
+ label: `Send #${i + 1}`,
129
+ action: `vibe dm @${them} ${starter}`,
130
+ description: starter.slice(0, 40) + (starter.length > 40 ? '...' : '')
131
+ })));
132
+ response.conversationStarters = starters;
133
+ response.context = starterContext;
134
+ }
63
135
 
64
- Say "message ${them} hello" to start`
65
- };
136
+ return response;
66
137
  }
67
138
 
68
- // Check if they're an agent (from first message if available)
69
- const theirMessage = thread.find(m => m.from === them);
70
- const agentIndicator = theirMessage?.isAgent ? ' ๐Ÿค–' : '';
139
+ // Get latest message from them for the summary preview
140
+ const latestFromThem = theirMessages.length > 0
141
+ ? theirMessages.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0))[0]
142
+ : null;
143
+
144
+ // Build the preview summary line (what appears in collapsed view)
145
+ let display = '';
146
+
147
+ // Header with presence status
148
+ const statusSuffix = presenceStatus ? ` ยท _${presenceStatus}_` : '';
71
149
 
72
- let display = `## Thread with @${them}${agentIndicator}\n\n`;
150
+ if (latestFromThem) {
151
+ const agentBadge = latestFromThem.isAgent ? ' ๐Ÿค–' : '';
152
+ const time = store.formatTimeAgo(latestFromThem.timestamp);
153
+ const preview = latestFromThem.body
154
+ ? summarizeMessage(latestFromThem.body)
155
+ : (latestFromThem.payload ? '[attachment]' : '');
73
156
 
74
- thread.forEach(m => {
157
+ display = `๐Ÿ’ฌ @${them}${agentBadge}${statusSuffix}\n> (${time}): "${preview}"\n\n`;
158
+ } else {
159
+ // No messages from them yet - you sent first
160
+ display = `๐Ÿ’ฌ @${them}${statusSuffix}\n> _Waiting for reply..._\n\n`;
161
+ }
162
+
163
+ // Thread section - sorted newest first so their latest message appears at top
164
+ display += `---\n๐Ÿ“œ Thread\n\n`;
165
+
166
+ const sortedThread = [...thread].sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
167
+
168
+ // Build a map for reply lookups
169
+ const messageMap = {};
170
+ thread.forEach(m => { messageMap[m.id] = m; });
171
+
172
+ sortedThread.forEach(m => {
75
173
  const isMe = m.from === myHandle;
76
174
  const agentBadge = m.isAgent && !isMe ? '๐Ÿค– ' : '';
77
175
  const sender = isMe ? 'you' : `@${m.from}`;
78
176
  const time = store.formatTimeAgo(m.timestamp);
79
177
 
80
- display += `${agentBadge}**${sender}** โ€” _${time}_\n`;
178
+ // Status indicator for sent messages: โœ“ sent, โœ“โœ“ delivered, โœ“โœ“ read
179
+ let statusIndicator = '';
180
+ if (isMe) {
181
+ if (m.status === 'read' || m.readByThem) {
182
+ statusIndicator = ' โœ“โœ“ read';
183
+ } else if (m.status === 'delivered' || m.delivered) {
184
+ statusIndicator = ' โœ“โœ“';
185
+ } else {
186
+ statusIndicator = ' โœ“';
187
+ }
188
+ }
189
+
190
+ display += `${agentBadge}**${sender}** โ€” _${time}${statusIndicator}_\n`;
191
+
192
+ // Show reply context if this is a threaded reply
193
+ if (m.reply_to_id || m.replyToId) {
194
+ const replyToId = m.reply_to_id || m.replyToId;
195
+ const originalMsg = messageMap[replyToId];
196
+ if (originalMsg) {
197
+ const originalSender = originalMsg.from === myHandle ? 'you' : `@${originalMsg.from}`;
198
+ const preview = (originalMsg.body || '').substring(0, 50);
199
+ display += `> โ†ฉ replying to ${originalSender}: "${preview}${preview.length >= 50 ? '...' : ''}"\n`;
200
+ } else {
201
+ display += `> โ†ฉ _replying to earlier message_\n`;
202
+ }
203
+ }
81
204
 
82
205
  // Show text if present
83
206
  if (m.body) {
@@ -114,6 +237,14 @@ Say "message ${them} hello" to start`
114
237
  response.reason = 'long_thread';
115
238
  }
116
239
 
240
+ // Add smart reply actions based on their last message
241
+ if (latestFromThem && latestFromThem.body) {
242
+ response.actions = formatActions(actions.afterOpenThread(them, latestFromThem.body));
243
+ } else {
244
+ // No message from them yet - offer generic options
245
+ response.actions = formatActions(actions.afterOpenThread(them, ''));
246
+ }
247
+
117
248
  return response;
118
249
  }
119
250
 
@@ -15,7 +15,7 @@ const werewolf = require('../games/werewolf');
15
15
 
16
16
  // Post game results to board
17
17
  async function postPartyGameResult(game, message) {
18
- const API_URL = process.env.VIBE_API_URL || 'https://slashvibe.dev';
18
+ const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
19
19
 
20
20
  try {
21
21
  await fetch(`${API_URL}/api/board`, {
@@ -103,7 +103,7 @@ async function handler(args) {
103
103
  const gameKey = getGameKey(game, room || myHandle);
104
104
 
105
105
  // Get or create game state
106
- let gameState = activeGames[gameKey];
106
+ const gameState = activeGames[gameKey];
107
107
 
108
108
  if (game === 'twotruths') {
109
109
  return handleTwoTruths(args, myHandle, gameKey, gameState);
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 };
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  const config = require('../config');
9
+ const analytics = require('../analytics');
9
10
 
10
11
  const definition = {
11
12
  name: 'vibe_presence_agent',
@@ -81,6 +82,9 @@ ${running ? '**Commands:**\n- `mute vibe for 1 hour` - Pause alerts\n- `stop pre
81
82
  config.set('presenceAgentRunning', true);
82
83
  config.set('presenceAgentStartedAt', Date.now());
83
84
 
85
+ // Track lurk mode enabled for retention analytics
86
+ analytics.trackLurkMode(true);
87
+
84
88
  return {
85
89
  display: `## ๐ŸŸข Presence Monitor Started
86
90
 
@@ -141,6 +145,9 @@ You can keep working - I'll alert you when something important happens.`,
141
145
  config.set('presenceAgentRunning', false);
142
146
  config.set('presenceAgentStartedAt', null);
143
147
 
148
+ // Track lurk mode disabled for retention analytics
149
+ analytics.trackLurkMode(false);
150
+
144
151
  return {
145
152
  display: `## โšช Presence Monitor Stopped${durationText}
146
153