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/chat.js ADDED
@@ -0,0 +1,202 @@
1
+ /**
2
+ * vibe chat — Live chat during broadcasts
3
+ *
4
+ * For broadcasters: See chat messages and reactions from viewers
5
+ * For viewers: Send messages to active broadcasts
6
+ *
7
+ * Commands:
8
+ * - chat → Show recent chat (if broadcasting or watching)
9
+ * - chat "message" → Send message to current broadcast
10
+ * - chat --room room_xxx → Show chat for specific room
11
+ * - chat "message" @handle → Send to someone's broadcast
12
+ */
13
+
14
+ const config = require('../config');
15
+
16
+ const definition = {
17
+ name: 'vibe_chat',
18
+ description: 'See and send chat messages during live broadcasts. Broadcasters see viewer engagement in real-time.',
19
+ inputSchema: {
20
+ type: 'object',
21
+ properties: {
22
+ message: {
23
+ type: 'string',
24
+ description: 'Message to send (omit to just view chat)'
25
+ },
26
+ room: {
27
+ type: 'string',
28
+ description: 'Room ID to view/send to'
29
+ },
30
+ target: {
31
+ type: 'string',
32
+ description: 'Handle of broadcaster to message (@user)'
33
+ },
34
+ limit: {
35
+ type: 'number',
36
+ description: 'Number of messages to show (default: 15)'
37
+ }
38
+ }
39
+ }
40
+ };
41
+
42
+ async function handler(args) {
43
+ const myHandle = config.getHandle();
44
+ const apiUrl = config.getApiUrl();
45
+ const limit = args.limit || 15;
46
+
47
+ // Determine which room we're interacting with
48
+ let roomId = args.room;
49
+ const targetHandle = args.target?.replace('@', '').toLowerCase();
50
+
51
+ // If no room specified, check if we're broadcasting
52
+ if (!roomId) {
53
+ const activeBroadcast = config.get('broadcast');
54
+ if (activeBroadcast) {
55
+ roomId = activeBroadcast.roomId;
56
+ }
57
+ }
58
+
59
+ // If target handle specified, find their room
60
+ if (targetHandle && !roomId) {
61
+ try {
62
+ const liveResponse = await fetch(`${apiUrl}/api/live`, {
63
+ headers: { 'Accept': 'application/json' }
64
+ });
65
+ const liveData = await liveResponse.json();
66
+
67
+ if (liveData.success && liveData.broadcasts) {
68
+ const broadcast = liveData.broadcasts.find(
69
+ b => b.handle.toLowerCase() === targetHandle
70
+ );
71
+ if (broadcast) {
72
+ roomId = broadcast.roomId;
73
+ } else {
74
+ return {
75
+ display: `📺 @${targetHandle} is not live right now.\n\n_Check who's streaming with \`vibe watch\`_`
76
+ };
77
+ }
78
+ }
79
+ } catch (error) {
80
+ return { display: `## Error\n\n${error.message}` };
81
+ }
82
+ }
83
+
84
+ // If still no room, show error
85
+ if (!roomId) {
86
+ let display = `💬 **No Active Broadcast**\n\n`;
87
+ display += `To view/send chat, either:\n`;
88
+ display += `1. Start broadcasting: \`vibe broadcast start\`\n`;
89
+ display += `2. Specify a room: \`vibe chat --room room_xxx\`\n`;
90
+ display += `3. Specify a user: \`vibe chat @handle\``;
91
+ return { display };
92
+ }
93
+
94
+ // SEND message
95
+ if (args.message) {
96
+ if (!myHandle) {
97
+ return { display: '⚠️ Not initialized. Run `vibe init` first to send messages.' };
98
+ }
99
+
100
+ try {
101
+ const response = await fetch(`${apiUrl}/api/watch/chat`, {
102
+ method: 'POST',
103
+ headers: { 'Content-Type': 'application/json' },
104
+ body: JSON.stringify({
105
+ roomId,
106
+ message: args.message,
107
+ handle: myHandle
108
+ })
109
+ });
110
+
111
+ const data = await response.json();
112
+
113
+ if (!data.success) {
114
+ if (data.error?.includes('not found')) {
115
+ return { display: `📺 Broadcast has ended.` };
116
+ }
117
+ return { display: `⚠️ Failed to send: ${data.error}` };
118
+ }
119
+
120
+ return {
121
+ display: `💬 Sent to chat: "${args.message}"`
122
+ };
123
+
124
+ } catch (error) {
125
+ return { display: `## Chat Error\n\n${error.message}` };
126
+ }
127
+ }
128
+
129
+ // VIEW chat and reactions
130
+ try {
131
+ // Fetch chat and reactions in parallel
132
+ const [chatResponse, reactResponse, broadcastResponse] = await Promise.all([
133
+ fetch(`${apiUrl}/api/watch/chat?room=${roomId}&limit=${limit}`),
134
+ fetch(`${apiUrl}/api/watch/react?room=${roomId}&limit=20`),
135
+ fetch(`${apiUrl}/api/watch?room=${roomId}`)
136
+ ]);
137
+
138
+ const chatData = await chatResponse.json();
139
+ const reactData = await reactResponse.json();
140
+ const broadcastData = await broadcastResponse.json();
141
+
142
+ if (!broadcastData.success) {
143
+ return { display: `📺 Broadcast not found or ended.` };
144
+ }
145
+
146
+ const broadcast = broadcastData.broadcast;
147
+ const messages = chatData.messages || [];
148
+ const reactions = reactData.reactions || [];
149
+
150
+ // Build display
151
+ let display = `💬 **Chat** — @${broadcast.handle}'s broadcast\n`;
152
+ display += `👥 ${broadcast.viewerCount || 0} watching`;
153
+ if (chatData.messagesPerMinute > 0) {
154
+ display += ` · ${chatData.messagesPerMinute} msg/min`;
155
+ }
156
+ display += `\n\n`;
157
+
158
+ // Recent reactions summary
159
+ if (reactions.length > 0) {
160
+ const reactionCounts = {};
161
+ for (const r of reactions.slice(0, 20)) {
162
+ reactionCounts[r.emoji] = (reactionCounts[r.emoji] || 0) + 1;
163
+ }
164
+ const reactionStr = Object.entries(reactionCounts)
165
+ .map(([emoji, count]) => `${emoji}×${count}`)
166
+ .join(' ');
167
+ display += `**Reactions:** ${reactionStr}\n\n`;
168
+ }
169
+
170
+ // Chat messages
171
+ if (messages.length === 0) {
172
+ display += `_No messages yet. Be the first to chat!_\n`;
173
+ display += `\`vibe chat "your message"\``;
174
+ } else {
175
+ display += `**Recent Messages:**\n`;
176
+ for (const msg of messages.slice(-limit)) {
177
+ const time = formatTime(msg.timestamp);
178
+ const badge = msg.isBroadcaster ? ' 🎙️' : '';
179
+ display += `[${time}] **@${msg.handle}**${badge}: ${msg.message}\n`;
180
+ }
181
+ }
182
+
183
+ display += `\n---\n`;
184
+ display += `_Reply: \`vibe chat "your message"\`_`;
185
+
186
+ return { display };
187
+
188
+ } catch (error) {
189
+ return { display: `## Chat Error\n\n${error.message}` };
190
+ }
191
+ }
192
+
193
+ function formatTime(timestamp) {
194
+ const date = new Date(timestamp);
195
+ return date.toLocaleTimeString('en-US', {
196
+ hour: 'numeric',
197
+ minute: '2-digit',
198
+ hour12: true
199
+ });
200
+ }
201
+
202
+ module.exports = { definition, handler };
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Collaborative Drawing — Create or join a shared drawing session
3
+ *
4
+ * Multiple users can draw together on a shared canvas in real-time.
5
+ * Perfect for sketching, doodling, or playing Pictionary together!
6
+ */
7
+
8
+ const config = require('../config');
9
+ const store = require('../store');
10
+ const { requireInit, normalizeHandle } = require('./_shared');
11
+ const drawing = require('../games/drawing');
12
+
13
+ const definition = {
14
+ name: 'vibe_collaborative_drawing',
15
+ description: 'Create or join a collaborative drawing session with multiple users. Draw together on a shared canvas!',
16
+ inputSchema: {
17
+ type: 'object',
18
+ properties: {
19
+ room: {
20
+ type: 'string',
21
+ description: 'Drawing room name (creates if doesn\'t exist, e.g., "art-party", "sketch-together")'
22
+ },
23
+ action: {
24
+ type: 'string',
25
+ description: 'Action to take',
26
+ enum: ['view', 'draw', 'line', 'clear', 'theme']
27
+ },
28
+ x: {
29
+ type: 'number',
30
+ description: 'X coordinate (0-19 for draw/line actions)'
31
+ },
32
+ y: {
33
+ type: 'number',
34
+ description: 'Y coordinate (0-11 for draw/line actions)'
35
+ },
36
+ x1: {
37
+ type: 'number',
38
+ description: 'End X coordinate (for line action)'
39
+ },
40
+ y1: {
41
+ type: 'number',
42
+ description: 'End Y coordinate (for line action)'
43
+ },
44
+ char: {
45
+ type: 'string',
46
+ description: 'Character/emoji to draw (e.g., "star", "heart", "tree"). See available chars in canvas display.'
47
+ },
48
+ theme: {
49
+ type: 'string',
50
+ description: 'Drawing theme/prompt to set for the room'
51
+ }
52
+ },
53
+ required: ['room']
54
+ }
55
+ };
56
+
57
+ // Storage key for drawing sessions
58
+ function getDrawingRoomKey(roomName) {
59
+ return `drawing_room:${roomName.toLowerCase()}`;
60
+ }
61
+
62
+ // Get or create drawing room
63
+ async function getOrCreateRoom(roomName) {
64
+ const key = getDrawingRoomKey(roomName);
65
+
66
+ try {
67
+ const existing = await store.kv.get(key);
68
+ if (existing) {
69
+ return JSON.parse(existing);
70
+ }
71
+ } catch (e) {
72
+ console.log(`[drawing] Creating new room: ${roomName}`);
73
+ }
74
+
75
+ // Create new room
76
+ const newRoom = drawing.createInitialDrawingState();
77
+ await store.kv.set(key, JSON.stringify(newRoom));
78
+ return newRoom;
79
+ }
80
+
81
+ // Save room state
82
+ async function saveRoom(roomName, gameState) {
83
+ const key = getDrawingRoomKey(roomName);
84
+ await store.kv.set(key, JSON.stringify(gameState));
85
+ }
86
+
87
+ // Post drawing activity to board
88
+ async function postDrawingActivity(roomName, playerHandle, action, details = '') {
89
+ const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
90
+
91
+ try {
92
+ let content = '';
93
+ switch (action) {
94
+ case 'created':
95
+ content = `🎨 @${playerHandle} created collaborative drawing room "${roomName}" — join in!`;
96
+ break;
97
+ case 'joined':
98
+ content = `🎨 @${playerHandle} joined the "${roomName}" drawing session`;
99
+ break;
100
+ case 'drew':
101
+ content = `🎨 @${playerHandle} is drawing in "${roomName}" ${details}`;
102
+ break;
103
+ case 'theme':
104
+ content = `🎯 @${playerHandle} set drawing theme in "${roomName}": ${details}`;
105
+ break;
106
+ default:
107
+ return; // Don't post for other actions
108
+ }
109
+
110
+ await fetch(`${API_URL}/api/board`, {
111
+ method: 'POST',
112
+ headers: { 'Content-Type': 'application/json' },
113
+ body: JSON.stringify({
114
+ author: 'echo',
115
+ content,
116
+ category: 'general'
117
+ })
118
+ });
119
+ } catch (e) {
120
+ console.error('[drawing] Failed to post to board:', e.message);
121
+ }
122
+ }
123
+
124
+ async function handler(args) {
125
+ const initCheck = requireInit();
126
+ if (initCheck) return initCheck;
127
+
128
+ const { room, action = 'view', x, y, x1, y1, char, theme } = args;
129
+ const myHandle = config.getHandle();
130
+
131
+ if (!room) {
132
+ return { display: 'Room name is required. Use something like "art-party" or "sketch-together"' };
133
+ }
134
+
135
+ // Normalize room name
136
+ const roomName = room.toLowerCase().replace(/[^a-z0-9-]/g, '-');
137
+
138
+ try {
139
+ // Get or create room
140
+ let gameState = await getOrCreateRoom(roomName);
141
+ const wasNewRoom = gameState.players.length === 0;
142
+
143
+ // Add player to room if not already there
144
+ if (!gameState.players.includes(myHandle)) {
145
+ const addResult = drawing.addPlayer(gameState, myHandle);
146
+ if (addResult.error) {
147
+ return { display: addResult.error };
148
+ }
149
+ gameState = addResult.gameState;
150
+ await saveRoom(roomName, gameState);
151
+
152
+ // Post activity
153
+ if (wasNewRoom) {
154
+ await postDrawingActivity(roomName, myHandle, 'created');
155
+ } else {
156
+ await postDrawingActivity(roomName, myHandle, 'joined');
157
+ }
158
+ }
159
+
160
+ // Handle different actions
161
+ switch (action) {
162
+ case 'view':
163
+ // Just show current state
164
+ break;
165
+
166
+ case 'draw':
167
+ if (x === undefined || y === undefined || !char) {
168
+ return { display: 'Draw action requires x, y coordinates and char. Example: `--action draw --x 10 --y 5 --char heart`' };
169
+ }
170
+
171
+ // Validate character name
172
+ const drawChar = drawing.DRAWING_CHARS[char];
173
+ if (!drawChar) {
174
+ const availableChars = Object.keys(drawing.DRAWING_CHARS).join(', ');
175
+ return { display: `Invalid character "${char}". Available: ${availableChars}` };
176
+ }
177
+
178
+ const drawResult = drawing.makeMove(gameState, x, y, drawChar, myHandle);
179
+ if (drawResult.error) {
180
+ return { display: drawResult.error };
181
+ }
182
+
183
+ gameState = drawResult.gameState;
184
+ await saveRoom(roomName, gameState);
185
+ await postDrawingActivity(roomName, myHandle, 'drew', `(${drawChar} at ${x},${y})`);
186
+ break;
187
+
188
+ case 'line':
189
+ if (x === undefined || y === undefined || x1 === undefined || y1 === undefined || !char) {
190
+ return { display: 'Line action requires x, y, x1, y1 coordinates and char. Example: `--action line --x 5 --y 5 --x1 15 --y1 8 --char star`' };
191
+ }
192
+
193
+ const lineChar = drawing.DRAWING_CHARS[char];
194
+ if (!lineChar) {
195
+ const availableChars = Object.keys(drawing.DRAWING_CHARS).join(', ');
196
+ return { display: `Invalid character "${char}". Available: ${availableChars}` };
197
+ }
198
+
199
+ const lineResult = drawing.drawLine(gameState, x, y, x1, y1, lineChar, myHandle);
200
+ if (lineResult.error) {
201
+ return { display: lineResult.error };
202
+ }
203
+
204
+ gameState = lineResult.gameState;
205
+ await saveRoom(roomName, gameState);
206
+ await postDrawingActivity(roomName, myHandle, 'drew', `line from (${x},${y}) to (${x1},${y1})`);
207
+ break;
208
+
209
+ case 'clear':
210
+ if (x === undefined || y === undefined) {
211
+ // Clear entire canvas
212
+ const clearResult = drawing.clearRegion(gameState, 0, 0, drawing.CANVAS_WIDTH - 1, drawing.CANVAS_HEIGHT - 1, myHandle);
213
+ if (clearResult.error) {
214
+ return { display: clearResult.error };
215
+ }
216
+ gameState = clearResult.gameState;
217
+ await postDrawingActivity(roomName, myHandle, 'drew', '(cleared canvas)');
218
+ } else {
219
+ // Clear specific region
220
+ const clearX1 = x1 !== undefined ? x1 : x;
221
+ const clearY1 = y1 !== undefined ? y1 : y;
222
+ const clearResult = drawing.clearRegion(gameState, x, y, clearX1, clearY1, myHandle);
223
+ if (clearResult.error) {
224
+ return { display: clearResult.error };
225
+ }
226
+ gameState = clearResult.gameState;
227
+ await postDrawingActivity(roomName, myHandle, 'drew', `(cleared region ${x},${y} to ${clearX1},${clearY1})`);
228
+ }
229
+ await saveRoom(roomName, gameState);
230
+ break;
231
+
232
+ case 'theme':
233
+ if (!theme) {
234
+ return { display: 'Theme action requires a theme. Example: `--action theme --theme "draw a house"`' };
235
+ }
236
+
237
+ const themeResult = drawing.setTheme(gameState, theme, myHandle);
238
+ if (themeResult.error) {
239
+ return { display: themeResult.error };
240
+ }
241
+
242
+ gameState = themeResult.gameState;
243
+ await saveRoom(roomName, gameState);
244
+ await postDrawingActivity(roomName, myHandle, 'theme', theme);
245
+ break;
246
+
247
+ default:
248
+ return { display: `Unknown action "${action}". Use: view, draw, line, clear, or theme` };
249
+ }
250
+
251
+ // Format and return display
252
+ const display = drawing.formatDrawingDisplay(gameState);
253
+ const stats = drawing.getCanvasStats(gameState);
254
+
255
+ let result = `# 🎨 Collaborative Drawing Room: "${roomName}"\n\n${display}`;
256
+
257
+ // Add usage instructions
258
+ result += '\n**How to draw:**\n';
259
+ result += `• \`vibe collaborative-drawing --room "${roomName}" --action draw --x 10 --y 5 --char heart\`\n`;
260
+ result += `• \`vibe collaborative-drawing --room "${roomName}" --action line --x 5 --y 3 --x1 15 --y1 8 --char star\`\n`;
261
+ result += `• \`vibe collaborative-drawing --room "${roomName}" --action clear\` (clear all)\n`;
262
+ result += `• \`vibe collaborative-drawing --room "${roomName}" --action theme --theme "draw a sunset"\`\n\n`;
263
+
264
+ // Add tips if there's a theme
265
+ if (gameState.theme) {
266
+ const tips = drawing.getDrawingTips(gameState.theme);
267
+ result += `**Drawing tips for "${gameState.theme}":**\n`;
268
+ result += tips.map(tip => `• ${tip}`).join('\n') + '\n\n';
269
+ }
270
+
271
+ // Add stats
272
+ result += `**Canvas stats:** ${stats.fillPercentage}% filled, ${stats.totalMoves} moves, ${stats.uniqueCharsUsed} different characters used\n`;
273
+
274
+ if (stats.mostUsedChar) {
275
+ result += `Most used: ${stats.mostUsedChar[0]} (${stats.mostUsedChar[1]} times)\n`;
276
+ }
277
+
278
+ return { display: result };
279
+
280
+ } catch (error) {
281
+ console.error('[drawing] Error:', error);
282
+ return { display: `Error: ${error.message}` };
283
+ }
284
+ }
285
+
286
+ module.exports = { definition, handler };
@@ -0,0 +1,178 @@
1
+ /**
2
+ * vibe_connection_status - Check connection health
3
+ *
4
+ * Shows if you're connected to the /vibe network and API health.
5
+ * Helps debug connection issues.
6
+ */
7
+
8
+ const config = require('../config');
9
+
10
+ const connectionStatusSchema = {
11
+ name: 'vibe_connection_status',
12
+ description: 'Check your connection status to /vibe. Shows API health, latency, and session info.',
13
+ inputSchema: {
14
+ type: 'object',
15
+ properties: {},
16
+ },
17
+ };
18
+
19
+ async function connectionStatusHandler() {
20
+ const handle = config.getHandle();
21
+ const apiUrl = config.getApiUrl();
22
+ const token = config.getToken();
23
+
24
+ const results = {
25
+ initialized: !!handle,
26
+ handle: handle || null,
27
+ apiUrl,
28
+ hasToken: !!token,
29
+ checks: {},
30
+ };
31
+
32
+ // Check API health
33
+ const healthStart = Date.now();
34
+ try {
35
+ const response = await fetch(`${apiUrl}/api/health`, {
36
+ signal: AbortSignal.timeout(5000),
37
+ });
38
+ const health = await response.json();
39
+ results.checks.api = {
40
+ status: health.status === 'healthy' ? '✅ healthy' : '⚠️ degraded',
41
+ latency: Date.now() - healthStart,
42
+ version: health.version,
43
+ };
44
+ } catch (e) {
45
+ results.checks.api = {
46
+ status: '❌ unreachable',
47
+ latency: Date.now() - healthStart,
48
+ error: e.message,
49
+ };
50
+ }
51
+
52
+ // Check auth (if we have a token)
53
+ if (token && handle) {
54
+ const authStart = Date.now();
55
+ try {
56
+ const response = await fetch(`${apiUrl}/api/v2/presence?user=${handle}`, {
57
+ headers: {
58
+ 'Authorization': `Bearer ${token}`,
59
+ },
60
+ signal: AbortSignal.timeout(5000),
61
+ });
62
+
63
+ if (response.status === 401) {
64
+ results.checks.auth = {
65
+ status: '❌ token expired',
66
+ latency: Date.now() - authStart,
67
+ action: 'Run `vibe init` to re-authenticate',
68
+ };
69
+ } else if (response.ok) {
70
+ results.checks.auth = {
71
+ status: '✅ authenticated',
72
+ latency: Date.now() - authStart,
73
+ };
74
+ } else {
75
+ results.checks.auth = {
76
+ status: '⚠️ error',
77
+ latency: Date.now() - authStart,
78
+ httpStatus: response.status,
79
+ };
80
+ }
81
+ } catch (e) {
82
+ results.checks.auth = {
83
+ status: '❌ failed',
84
+ latency: Date.now() - authStart,
85
+ error: e.message,
86
+ };
87
+ }
88
+ }
89
+
90
+ // Check presence write (can we heartbeat?)
91
+ if (token && handle) {
92
+ const presenceStart = Date.now();
93
+ try {
94
+ const response = await fetch(`${apiUrl}/api/presence`, {
95
+ method: 'POST',
96
+ headers: {
97
+ 'Content-Type': 'application/json',
98
+ 'Authorization': `Bearer ${token}`,
99
+ },
100
+ body: JSON.stringify({
101
+ username: handle,
102
+ workingOn: 'Checking connection status',
103
+ }),
104
+ signal: AbortSignal.timeout(5000),
105
+ });
106
+
107
+ if (response.ok) {
108
+ results.checks.presence = {
109
+ status: '✅ online',
110
+ latency: Date.now() - presenceStart,
111
+ };
112
+ } else {
113
+ results.checks.presence = {
114
+ status: '⚠️ error',
115
+ latency: Date.now() - presenceStart,
116
+ httpStatus: response.status,
117
+ };
118
+ }
119
+ } catch (e) {
120
+ results.checks.presence = {
121
+ status: '❌ offline',
122
+ latency: Date.now() - presenceStart,
123
+ error: e.message,
124
+ };
125
+ }
126
+ }
127
+
128
+ // Format output
129
+ let output = '## /vibe Connection Status\n\n';
130
+
131
+ if (!results.initialized) {
132
+ output += '❌ **Not initialized**\n';
133
+ output += 'Run `vibe init` to get started.\n';
134
+ return { content: [{ type: 'text', text: output }] };
135
+ }
136
+
137
+ output += `**Handle:** @${results.handle}\n`;
138
+ output += `**API:** ${results.apiUrl}\n\n`;
139
+
140
+ output += '### Health Checks\n\n';
141
+
142
+ for (const [name, check] of Object.entries(results.checks)) {
143
+ output += `**${name}:** ${check.status}`;
144
+ if (check.latency) {
145
+ output += ` (${check.latency}ms)`;
146
+ }
147
+ output += '\n';
148
+
149
+ if (check.error) {
150
+ output += ` _Error: ${check.error}_\n`;
151
+ }
152
+ if (check.action) {
153
+ output += ` → ${check.action}\n`;
154
+ }
155
+ }
156
+
157
+ // Overall status
158
+ const allHealthy = Object.values(results.checks).every(c =>
159
+ c.status.includes('✅')
160
+ );
161
+
162
+ output += '\n';
163
+ if (allHealthy) {
164
+ output += '🟢 **All systems operational**\n';
165
+ } else {
166
+ output += '🟡 **Some issues detected** — check details above\n';
167
+ }
168
+
169
+ return {
170
+ content: [{ type: 'text', text: output }],
171
+ connectionStatus: results,
172
+ };
173
+ }
174
+
175
+ module.exports = {
176
+ schema: connectionStatusSchema,
177
+ handler: connectionStatusHandler,
178
+ };
@@ -19,7 +19,7 @@ const DAILY_ROOM_PREFIX = 'daily-';
19
19
 
20
20
  const definition = {
21
21
  name: 'vibe_crossword',
22
- description: 'Multiplayer crossword puzzles. Search triggers: "crossword", "puzzle", "word game", "daily challenge", "brain teaser". Daily 5x5 minis with collaborative and competitive modes.',
22
+ description: 'Play multiplayer crossword puzzles. Daily 5x5 minis, collaborative or competitive.',
23
23
  inputSchema: {
24
24
  type: 'object',
25
25
  properties: {
@@ -200,6 +200,22 @@ function listRooms() {
200
200
 
201
201
  // Handler
202
202
  async function handler(args) {
203
+ // Check if crossword module is available
204
+ if (!crossword.isAvailable()) {
205
+ return {
206
+ display: `**Crossword not available**
207
+
208
+ The crossword-layout-generator module isn't installed.
209
+
210
+ **To fix:**
211
+ \`\`\`bash
212
+ cd ~/.vibe/vibe-repo/mcp-server && npm install
213
+ \`\`\`
214
+
215
+ Then restart Claude Code.`
216
+ };
217
+ }
218
+
203
219
  // Require init
204
220
  const initCheck = requireInit();
205
221
  if (initCheck) {