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.
- package/README.md +1 -0
- package/analytics.js +107 -0
- package/auth-store.js +148 -0
- package/auto-update.js +130 -0
- package/bridges/bridge-monitor.js +388 -0
- package/bridges/discord-bot.js +431 -0
- package/bridges/farcaster.js +299 -0
- package/bridges/telegram.js +261 -0
- package/bridges/webhook-health.js +420 -0
- package/bridges/webhook-server.js +437 -0
- package/bridges/whatsapp.js +441 -0
- package/bridges/x-webhook.js +423 -0
- package/config.js +154 -5
- package/crypto.js +3 -8
- package/games/arcade.js +406 -0
- package/games/chess.js +451 -0
- package/games/colorguess.js +343 -0
- package/games/crossword-words.js +171 -0
- package/games/crossword.js +461 -0
- package/games/drawing.js +347 -0
- package/games/gameroulette.js +300 -0
- package/games/gamerouter.js +336 -0
- package/games/gamestatus.js +337 -0
- package/games/guessnumber.js +209 -0
- package/games/hangman.js +279 -0
- package/games/memory.js +338 -0
- package/games/multiplayer-tictactoe.js +389 -0
- package/games/pixelart.js +399 -0
- package/games/quickduel.js +354 -0
- package/games/riddle.js +371 -0
- package/games/rockpaperscissors.js +291 -0
- package/games/snake.js +406 -0
- package/games/storybuilder.js +343 -0
- package/games/tictactoe.js +345 -0
- package/games/twentyquestions.js +286 -0
- package/games/twotruths.js +207 -0
- package/games/werewolf.js +508 -0
- package/games/wordassociation.js +247 -0
- package/games/wordchain.js +135 -0
- package/index.js +105 -216
- package/intelligence/index.js +45 -0
- package/intelligence/infer.js +316 -0
- package/intelligence/interests.js +369 -0
- package/intelligence/patterns.js +651 -0
- package/intelligence/proactive.js +358 -0
- package/intelligence/serendipity.js +306 -0
- package/memory.js +166 -0
- package/notification-emitter.js +77 -0
- package/notify.js +102 -1
- package/package.json +21 -7
- package/prompts.js +1 -1
- package/protocol/index.js +161 -1
- package/setup.js +402 -0
- package/store/api.js +528 -82
- package/store/profiles.js +160 -12
- package/tools/_actions.js +463 -16
- package/tools/_deprecated/auto-suggest-connections.js +304 -0
- package/tools/_deprecated/bootstrap-skills.js +231 -0
- package/tools/_deprecated/bridge-dashboard.js +342 -0
- package/tools/_deprecated/bridge-health.js +400 -0
- package/tools/_deprecated/bridge-live.js +384 -0
- package/tools/_deprecated/bridges.js +383 -0
- package/tools/_deprecated/colorguess.js +281 -0
- package/tools/_deprecated/discover-insights.js +379 -0
- package/tools/_deprecated/discover-momentum.js +256 -0
- package/tools/_deprecated/discovery-analytics.js +345 -0
- package/tools/_deprecated/discovery-auto-suggest.js +275 -0
- package/tools/_deprecated/discovery-bootstrap.js +267 -0
- package/tools/_deprecated/discovery-daily.js +375 -0
- package/tools/_deprecated/discovery-dashboard.js +385 -0
- package/tools/_deprecated/discovery-digest.js +314 -0
- package/tools/_deprecated/discovery-hub.js +357 -0
- package/tools/_deprecated/discovery-insights.js +384 -0
- package/tools/_deprecated/discovery-momentum.js +281 -0
- package/tools/_deprecated/discovery-monitor.js +319 -0
- package/tools/_deprecated/discovery-proactive.js +300 -0
- package/tools/_deprecated/draw.js +317 -0
- package/tools/_deprecated/farcaster.js +307 -0
- package/tools/_deprecated/forget.js +119 -0
- package/tools/_deprecated/games-catalog.js +376 -0
- package/tools/_deprecated/games.js +313 -0
- package/tools/_deprecated/guessnumber.js +194 -0
- package/tools/_deprecated/hangman.js +129 -0
- package/tools/_deprecated/multiplayer-tictactoe.js +303 -0
- package/tools/_deprecated/recall.js +147 -0
- package/tools/_deprecated/remember.js +86 -0
- package/tools/_deprecated/riddle.js +240 -0
- package/tools/_deprecated/run-bootstrap.js +69 -0
- package/tools/_deprecated/skills-analytics.js +349 -0
- package/tools/_deprecated/skills-bootstrap.js +301 -0
- package/tools/_deprecated/skills-dashboard.js +268 -0
- package/tools/_deprecated/skills.js +380 -0
- package/tools/_deprecated/smart-intro.js +353 -0
- package/tools/_deprecated/storybuilder.js +331 -0
- package/tools/_deprecated/telegram-bot.js +183 -0
- package/tools/_deprecated/telegram-setup.js +214 -0
- package/tools/_deprecated/twentyquestions.js +143 -0
- package/tools/_discovery-enhanced.js +290 -0
- package/tools/_discovery.js +439 -0
- package/tools/_proactive-discovery.js +301 -0
- package/tools/_shared/index.js +64 -0
- package/tools/_shared.js +234 -0
- package/tools/_work-context.js +338 -0
- package/tools/_work-context.manual-test.js +199 -0
- package/tools/_work-context.test.js +260 -0
- package/tools/activity.js +220 -0
- package/tools/admin-inbox.js +218 -0
- package/tools/agent-treasury.js +288 -0
- package/tools/analytics.js +191 -0
- package/tools/approve.js +197 -0
- package/tools/arcade.js +173 -0
- package/tools/artifact-create.js +17 -11
- package/tools/artifact-view.js +31 -1
- package/tools/artifacts-price.js +47 -43
- package/tools/ask-expert.js +160 -0
- package/tools/available.js +120 -0
- package/tools/become-expert.js +150 -0
- package/tools/broadcast.js +286 -0
- package/tools/chat.js +202 -0
- package/tools/collaborative-drawing.js +286 -0
- package/tools/connection-status.js +178 -0
- package/tools/crossword.js +17 -1
- package/tools/discover.js +350 -34
- package/tools/dm.js +115 -73
- package/tools/drawing.js +1 -1
- package/tools/earnings.js +126 -0
- package/tools/echo.js +16 -0
- package/tools/feed.js +51 -7
- package/tools/follow.js +224 -0
- package/tools/friends.js +207 -0
- package/tools/game.js +2 -2
- package/tools/genesis.js +233 -0
- package/tools/gig-browse.js +206 -0
- package/tools/gig-complete.js +144 -0
- package/tools/handoff.js +7 -1
- package/tools/help.js +4 -4
- package/tools/idea.js +9 -2
- package/tools/inbox.js +334 -28
- package/tools/init.js +727 -36
- package/tools/invite.js +15 -4
- package/tools/l2-bridge.js +272 -0
- package/tools/l2-status.js +217 -0
- package/tools/l2.js +206 -0
- package/tools/migrate.js +156 -0
- package/tools/mint.js +377 -0
- package/tools/multiplayer-game.js +1 -1
- package/tools/notifications.js +415 -0
- package/tools/observe.js +200 -0
- package/tools/onboarding.js +147 -0
- package/tools/open.js +143 -12
- package/tools/party-game.js +2 -2
- package/tools/plan.js +225 -0
- package/tools/presence-agent.js +7 -0
- package/tools/proof-of-work.js +100 -104
- package/tools/pulse.js +218 -0
- package/tools/reply.js +166 -0
- package/tools/report.js +2 -2
- package/tools/reputation.js +175 -0
- package/tools/request.js +17 -3
- package/tools/schedule.js +367 -0
- package/tools/search-messages.js +123 -0
- package/tools/session.js +420 -0
- package/tools/session_price.js +128 -0
- package/tools/settings.js +126 -3
- package/tools/ship.js +31 -8
- package/tools/shipback.js +326 -0
- package/tools/smart-check.js +201 -0
- package/tools/social-processor.js +445 -0
- package/tools/solo-game.js +1 -1
- package/tools/start.js +335 -93
- package/tools/status.js +53 -6
- package/tools/stuck.js +297 -0
- package/tools/subscribe.js +148 -0
- package/tools/subscriptions.js +134 -0
- package/tools/suggest-tags.js +6 -8
- package/tools/tag-suggestions.js +257 -0
- package/tools/tip.js +193 -0
- package/tools/token.js +103 -0
- package/tools/update.js +1 -1
- package/tools/wallet.js +239 -186
- package/tools/watch.js +157 -0
- package/tools/webhook-test.js +388 -0
- package/tools/who.js +54 -3
- package/tools/withdraw.js +145 -0
- package/tools/work-summary.js +96 -0
- package/tools/workshop.js +327 -0
- package/tools/x-mentions.js +1 -1
- package/version.json +14 -3
- package/tools/artifacts-buy.js +0 -111
- package/tools/connect.js +0 -284
- package/tools/gigs-apply.js +0 -99
- package/tools/gigs-browse.js +0 -114
- package/tools/gigs-complete.js +0 -128
- package/tools/gigs-post.js +0 -140
- package/tools/live-off.js +0 -109
- package/tools/live-watch.js +0 -176
- package/tools/live.js +0 -128
- package/tools/sessions-browse.js +0 -105
- package/tools/whats-happening.js +0 -125
- /package/tools/{away.js ā _deprecated/away.js} +0 -0
- /package/tools/{back.js ā _deprecated/back.js} +0 -0
- /package/tools/{mute.js ā _deprecated/mute.js} +0 -0
- /package/tools/{skills-exchange.js ā _deprecated/skills-exchange.js} +0 -0
- /package/tools/{tictactoe.js ā _deprecated/tictactoe.js} +0 -0
- /package/tools/{wordassociation.js ā _deprecated/wordassociation.js} +0 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe games ā Browse and launch workshop games
|
|
3
|
+
*
|
|
4
|
+
* Discover all available games, get recommendations, and launch games
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const config = require('../config');
|
|
8
|
+
const store = require('../store');
|
|
9
|
+
const { createGamePayload, formatPayload } = require('../protocol');
|
|
10
|
+
const { requireInit, normalizeHandle } = require('./_shared');
|
|
11
|
+
|
|
12
|
+
// Import game systems
|
|
13
|
+
const arcade = require('../games/arcade');
|
|
14
|
+
const gamerouter = require('../games/gamerouter');
|
|
15
|
+
|
|
16
|
+
// Track active game sessions per user
|
|
17
|
+
const activeSessions = new Map();
|
|
18
|
+
|
|
19
|
+
const definition = {
|
|
20
|
+
name: 'vibe_games',
|
|
21
|
+
description: 'Browse and launch workshop games. Discover new games, get recommendations, or launch any game.',
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
action: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'What to do: browse, launch, suggest, help, status',
|
|
28
|
+
enum: ['browse', 'launch', 'suggest', 'help', 'status', 'quit']
|
|
29
|
+
},
|
|
30
|
+
game: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: 'Game to launch (e.g., hangman, wordchain, drawing)'
|
|
33
|
+
},
|
|
34
|
+
category: {
|
|
35
|
+
type: 'string',
|
|
36
|
+
description: 'Game category to browse (classic, word, puzzle, action, creative, social)'
|
|
37
|
+
},
|
|
38
|
+
difficulty: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
description: 'Difficulty preference for suggestions (easy, medium, hard)',
|
|
41
|
+
enum: ['easy', 'medium', 'hard']
|
|
42
|
+
},
|
|
43
|
+
players: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
description: 'Player preference for suggestions (solo, multiplayer)',
|
|
46
|
+
enum: ['solo', 'multiplayer']
|
|
47
|
+
},
|
|
48
|
+
command: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
description: 'Command to send to active game'
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
required: []
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Get user session
|
|
58
|
+
function getUserSession(handle) {
|
|
59
|
+
if (!activeSessions.has(handle)) {
|
|
60
|
+
activeSessions.set(handle, {
|
|
61
|
+
currentGame: null,
|
|
62
|
+
gameState: null,
|
|
63
|
+
arcadeState: arcade.createInitialArcadeState(),
|
|
64
|
+
lastActivity: new Date().toISOString()
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return activeSessions.get(handle);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Clear user session
|
|
71
|
+
function clearUserSession(handle) {
|
|
72
|
+
activeSessions.delete(handle);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Format game list for display
|
|
76
|
+
function formatGameList(games, title = 'Games') {
|
|
77
|
+
if (!games || games.length === 0) {
|
|
78
|
+
return `**${title}**\n*No games found.*`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let display = `**${title}** (${games.length} games)\n\n`;
|
|
82
|
+
|
|
83
|
+
games.forEach(game => {
|
|
84
|
+
display += `${game.icon || 'š®'} **${game.name}**\n`;
|
|
85
|
+
display += ` *${game.description}*\n`;
|
|
86
|
+
display += ` Players: ${game.players} ⢠Difficulty: ${game.difficulty}\n\n`;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return display;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Launch a specific game
|
|
93
|
+
function launchGame(session, gameId) {
|
|
94
|
+
// Check if game exists
|
|
95
|
+
if (!gamerouter.gameAvailable(gameId)) {
|
|
96
|
+
const availableGames = gamerouter.getAvailableGames();
|
|
97
|
+
return {
|
|
98
|
+
error: `Game '${gameId}' not found! Available games: ${availableGames.join(', ')}`
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Create game state
|
|
103
|
+
const result = gamerouter.createGameState(gameId);
|
|
104
|
+
if (result.error) {
|
|
105
|
+
return { error: result.error };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Update session
|
|
109
|
+
session.currentGame = gameId;
|
|
110
|
+
session.gameState = result.gameState;
|
|
111
|
+
session.lastActivity = new Date().toISOString();
|
|
112
|
+
|
|
113
|
+
// Get display
|
|
114
|
+
const display = gamerouter.displayGame(gameId, result.gameState);
|
|
115
|
+
const instructions = gamerouter.getQuickStart(gameId);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
success: true,
|
|
119
|
+
display: display + '\n\nš” **Quick Start:** ' + instructions + '\n\n*Use `vibe games --command [action]` to play, or `vibe games --action quit` to exit.*'
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Handle game command
|
|
124
|
+
function handleGameCommand(session, command) {
|
|
125
|
+
if (!session.currentGame || !session.gameState) {
|
|
126
|
+
return { error: 'No active game. Use `vibe games --action launch --game [name]` to start a game.' };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const result = gamerouter.handleGameCommand(session.currentGame, session.gameState, command);
|
|
130
|
+
if (result.error) {
|
|
131
|
+
return { error: result.error };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Update session
|
|
135
|
+
if (result.gameState) {
|
|
136
|
+
session.gameState = result.gameState;
|
|
137
|
+
session.lastActivity = new Date().toISOString();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Format response
|
|
141
|
+
let display = '';
|
|
142
|
+
if (result.display) {
|
|
143
|
+
display = result.display;
|
|
144
|
+
} else if (result.gameState) {
|
|
145
|
+
display = gamerouter.displayGame(session.currentGame, result.gameState);
|
|
146
|
+
} else {
|
|
147
|
+
display = 'Command processed.';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { success: true, display: display };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Generate game recommendations
|
|
154
|
+
function getRecommendations(difficulty = 'any', players = 'any') {
|
|
155
|
+
const suggestions = gamerouter.getGameSuggestions(players, difficulty);
|
|
156
|
+
|
|
157
|
+
let display = 'šÆ **Game Recommendations**\n\n';
|
|
158
|
+
|
|
159
|
+
if (difficulty !== 'any') {
|
|
160
|
+
display += `*${difficulty.charAt(0).toUpperCase() + difficulty.slice(1)} difficulty games*\n`;
|
|
161
|
+
}
|
|
162
|
+
if (players !== 'any') {
|
|
163
|
+
display += `*${players.charAt(0).toUpperCase() + players.slice(1)} games*\n`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
display += '\n';
|
|
167
|
+
|
|
168
|
+
const recommendedGames = suggestions.slice(0, 6); // Show top 6 recommendations
|
|
169
|
+
|
|
170
|
+
recommendedGames.forEach(game => {
|
|
171
|
+
const status = game.available ? 'ā
' : 'š§';
|
|
172
|
+
display += `${status} ${game.icon || 'š®'} **${game.name}** (${game.players})\n`;
|
|
173
|
+
display += ` *${game.description}*\n`;
|
|
174
|
+
display += ` Launch with: \`vibe games --action launch --game ${game.id}\`\n\n`;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (suggestions.length > 6) {
|
|
178
|
+
display += `*... and ${suggestions.length - 6} more games! Use \`vibe games --action browse\` to see all.*\n`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return display;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Generate help text
|
|
185
|
+
function generateHelp() {
|
|
186
|
+
return `š® **Workshop Games Help**
|
|
187
|
+
|
|
188
|
+
**Actions:**
|
|
189
|
+
⢠\`vibe games --action browse\` - Browse all available games
|
|
190
|
+
⢠\`vibe games --action browse --category word\` - Browse word games
|
|
191
|
+
⢠\`vibe games --action launch --game hangman\` - Launch hangman game
|
|
192
|
+
⢠\`vibe games --action suggest\` - Get game recommendations
|
|
193
|
+
⢠\`vibe games --action suggest --difficulty easy --players solo\` - Filtered suggestions
|
|
194
|
+
⢠\`vibe games --action status\` - See your current game status
|
|
195
|
+
⢠\`vibe games --action quit\` - Quit current game
|
|
196
|
+
|
|
197
|
+
**Game Categories:**
|
|
198
|
+
š² classic ⢠š word ⢠𧩠puzzle ⢠┠action ⢠šØ creative ⢠š„ social
|
|
199
|
+
|
|
200
|
+
**While Playing:**
|
|
201
|
+
⢠\`vibe games --command [action]\` - Send command to current game
|
|
202
|
+
⢠Each game has its own commands - check the quick start guide!
|
|
203
|
+
|
|
204
|
+
**Popular Games:**
|
|
205
|
+
ā tic-tac-toe ⢠šÆ hangman ⢠š wordchain ⢠šØ drawing ⢠ā twentyquestions
|
|
206
|
+
|
|
207
|
+
*Type \`vibe games --action browse\` to see all ${gamerouter.getAvailableGames().length} available games!*`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function handler(args) {
|
|
211
|
+
const initCheck = requireInit();
|
|
212
|
+
if (initCheck) return initCheck;
|
|
213
|
+
|
|
214
|
+
const { action = 'browse', game, category, difficulty, players, command } = args;
|
|
215
|
+
const myHandle = config.getHandle();
|
|
216
|
+
const session = getUserSession(myHandle);
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
switch (action) {
|
|
220
|
+
case 'help':
|
|
221
|
+
return { display: generateHelp() };
|
|
222
|
+
|
|
223
|
+
case 'browse': {
|
|
224
|
+
if (category) {
|
|
225
|
+
// Browse specific category using arcade
|
|
226
|
+
const result = arcade.navigateToCategory(session.arcadeState, category);
|
|
227
|
+
if (result.error) {
|
|
228
|
+
return { display: result.error };
|
|
229
|
+
}
|
|
230
|
+
session.arcadeState = result.gameState;
|
|
231
|
+
const display = arcade.formatArcadeDisplay(session.arcadeState);
|
|
232
|
+
return { display };
|
|
233
|
+
} else {
|
|
234
|
+
// Browse all games using arcade
|
|
235
|
+
session.arcadeState = arcade.createInitialArcadeState();
|
|
236
|
+
const display = arcade.formatArcadeDisplay(session.arcadeState);
|
|
237
|
+
return { display };
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
case 'launch': {
|
|
242
|
+
if (!game) {
|
|
243
|
+
return { display: 'Please specify a game to launch. Use `vibe games --action browse` to see available games.' };
|
|
244
|
+
}
|
|
245
|
+
return launchGame(session, game);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
case 'suggest': {
|
|
249
|
+
const display = getRecommendations(difficulty, players);
|
|
250
|
+
return { display };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
case 'status': {
|
|
254
|
+
if (session.currentGame) {
|
|
255
|
+
const display = gamerouter.displayGame(session.currentGame, session.gameState);
|
|
256
|
+
return {
|
|
257
|
+
display: `**Current Game:** ${session.currentGame}\n\n${display}\n\n*Use \`vibe games --command [action]\` to play, or \`vibe games --action quit\` to exit.*`
|
|
258
|
+
};
|
|
259
|
+
} else {
|
|
260
|
+
const totalGames = gamerouter.getAvailableGames().length;
|
|
261
|
+
return {
|
|
262
|
+
display: `**No Active Game**\n\nš® **${totalGames} games available** in the Workshop\n\nUse \`vibe games --action launch --game [name]\` to start a game\nOr \`vibe games --action browse\` to explore all games`
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
case 'quit': {
|
|
268
|
+
if (session.currentGame) {
|
|
269
|
+
const gameName = session.currentGame;
|
|
270
|
+
session.currentGame = null;
|
|
271
|
+
session.gameState = null;
|
|
272
|
+
return { display: `ā
Quit ${gameName}. Use \`vibe games --action browse\` to find another game!` };
|
|
273
|
+
} else {
|
|
274
|
+
return { display: 'No active game to quit.' };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
default: {
|
|
279
|
+
// If we have a command and active game, handle it
|
|
280
|
+
if (command && session.currentGame) {
|
|
281
|
+
return handleGameCommand(session, command);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Default to browse if no specific action
|
|
285
|
+
session.arcadeState = arcade.createInitialArcadeState();
|
|
286
|
+
const display = arcade.formatArcadeDisplay(session.arcadeState);
|
|
287
|
+
return { display };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.error('[games] Error:', error);
|
|
293
|
+
return { display: `Error: ${error.message}. Try \`vibe games --action help\` for usage.` };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Clean up inactive sessions (called periodically)
|
|
298
|
+
function cleanupInactiveSessions() {
|
|
299
|
+
const now = Date.now();
|
|
300
|
+
const TIMEOUT = 30 * 60 * 1000; // 30 minutes
|
|
301
|
+
|
|
302
|
+
for (const [handle, session] of activeSessions.entries()) {
|
|
303
|
+
const lastActivity = new Date(session.lastActivity).getTime();
|
|
304
|
+
if (now - lastActivity > TIMEOUT) {
|
|
305
|
+
activeSessions.delete(handle);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Run cleanup every 10 minutes
|
|
311
|
+
setInterval(cleanupInactiveSessions, 10 * 60 * 1000);
|
|
312
|
+
|
|
313
|
+
module.exports = { definition, handler };
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe guessnumber ā Play guess the number game with someone
|
|
3
|
+
*
|
|
4
|
+
* A fun number guessing game with difficulty levels
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const config = require('../config');
|
|
8
|
+
const store = require('../store');
|
|
9
|
+
const { createGamePayload } = require('../protocol');
|
|
10
|
+
const { requireInit, normalizeHandle } = require('./_shared');
|
|
11
|
+
|
|
12
|
+
// Import game implementation
|
|
13
|
+
const guessNumber = require('../games/guessnumber');
|
|
14
|
+
|
|
15
|
+
// Post game results to board and Discord
|
|
16
|
+
async function postGameResult(winner, loser, moves, difficulty) {
|
|
17
|
+
const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
|
|
18
|
+
|
|
19
|
+
// Post to board
|
|
20
|
+
try {
|
|
21
|
+
const content = `@${winner} guessed the number in ${moves} moves! (${difficulty} difficulty) vs @${loser}`;
|
|
22
|
+
|
|
23
|
+
await fetch(`${API_URL}/api/board`, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: { 'Content-Type': 'application/json' },
|
|
26
|
+
body: JSON.stringify({
|
|
27
|
+
author: 'echo',
|
|
28
|
+
content,
|
|
29
|
+
category: 'general'
|
|
30
|
+
})
|
|
31
|
+
});
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.error('[guessnumber] Failed to post to board:', e.message);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Post to Discord
|
|
37
|
+
try {
|
|
38
|
+
await fetch(`${API_URL}/api/discord-bridge`, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: { 'Content-Type': 'application/json' },
|
|
41
|
+
body: JSON.stringify({
|
|
42
|
+
type: 'game',
|
|
43
|
+
data: {
|
|
44
|
+
game: 'guess-the-number',
|
|
45
|
+
winner: winner,
|
|
46
|
+
loser: loser,
|
|
47
|
+
player1: winner,
|
|
48
|
+
player2: loser,
|
|
49
|
+
moves: moves,
|
|
50
|
+
difficulty: difficulty,
|
|
51
|
+
draw: false
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
});
|
|
55
|
+
} catch (e) {
|
|
56
|
+
console.error('[guessnumber] Failed to post to Discord:', e.message);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const definition = {
|
|
61
|
+
name: 'vibe_guessnumber',
|
|
62
|
+
description: 'Play a guess the number game with someone. Choose difficulty: easy (1-10), medium (1-50), hard (1-100), extreme (1-1000)',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
handle: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
description: 'Who to play with (e.g., @solienne)'
|
|
69
|
+
},
|
|
70
|
+
difficulty: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
description: 'Game difficulty (default: medium)',
|
|
73
|
+
enum: ['easy', 'medium', 'hard', 'extreme']
|
|
74
|
+
},
|
|
75
|
+
guess: {
|
|
76
|
+
type: ['number', 'string'],
|
|
77
|
+
description: 'Your number guess'
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
required: ['handle']
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Parse game state from thread
|
|
86
|
+
*/
|
|
87
|
+
function getGameState(thread) {
|
|
88
|
+
// Find the most recent guess number game payload
|
|
89
|
+
for (let i = thread.length - 1; i >= 0; i--) {
|
|
90
|
+
const msg = thread[i];
|
|
91
|
+
if (msg.payload?.type === 'game' && msg.payload?.game === 'guessnumber') {
|
|
92
|
+
return msg.payload.state;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Format game display
|
|
100
|
+
*/
|
|
101
|
+
function formatGameDisplay(gameState, them) {
|
|
102
|
+
const display = guessNumber.formatGuessNumberDisplay(gameState);
|
|
103
|
+
|
|
104
|
+
if (gameState.gameOver && gameState.won) {
|
|
105
|
+
return `## Guess the Number with @${them}\n\n${display}\n\nGreat job! Start a new game anytime with \`vibe guessnumber @${them}\``;
|
|
106
|
+
} else if (gameState.gameOver && !gameState.won) {
|
|
107
|
+
return `## Guess the Number with @${them}\n\n${display}\n\nTry again! Start a new game with \`vibe guessnumber @${them}\``;
|
|
108
|
+
} else {
|
|
109
|
+
return `## Guess the Number with @${them}\n\n${display}\n\nMake your next guess with \`vibe guessnumber @${them} --guess NUMBER\``;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function handler(args) {
|
|
114
|
+
const initCheck = requireInit();
|
|
115
|
+
if (initCheck) return initCheck;
|
|
116
|
+
|
|
117
|
+
const { handle, guess } = args;
|
|
118
|
+
const difficulty = args.difficulty || 'medium';
|
|
119
|
+
const myHandle = config.getHandle();
|
|
120
|
+
const them = normalizeHandle(handle);
|
|
121
|
+
|
|
122
|
+
if (them === myHandle) {
|
|
123
|
+
return { display: 'You can\'t play a guessing game with yourself! That would be cheating! š' };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Get existing thread
|
|
127
|
+
const thread = await store.getThread(myHandle, them);
|
|
128
|
+
let gameState = getGameState(thread);
|
|
129
|
+
|
|
130
|
+
// Show current state if no guess provided
|
|
131
|
+
if (guess === undefined) {
|
|
132
|
+
if (!gameState) {
|
|
133
|
+
// Start new game
|
|
134
|
+
const newGameState = guessNumber.createInitialGuessNumberState(difficulty);
|
|
135
|
+
const payload = createGamePayload('guessnumber', newGameState);
|
|
136
|
+
|
|
137
|
+
const difficultyInfo = guessNumber.getDifficultyInfo()[difficulty];
|
|
138
|
+
await store.sendMessage(
|
|
139
|
+
myHandle,
|
|
140
|
+
them,
|
|
141
|
+
`Started a new guess the number game! (${difficulty}: ${difficultyInfo.range}) I'm thinking of a number... can you guess it?`,
|
|
142
|
+
'dm',
|
|
143
|
+
payload
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
display: `## New Guess the Number Game with @${them}\n\n${guessNumber.formatGuessNumberDisplay(newGameState)}\n\nUse \`vibe guessnumber @${them} --guess NUMBER\` to make your first guess!`
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Show existing game
|
|
152
|
+
return {
|
|
153
|
+
display: formatGameDisplay(gameState, them)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Make a guess
|
|
158
|
+
// Initialize game if needed
|
|
159
|
+
if (!gameState) {
|
|
160
|
+
gameState = guessNumber.createInitialGuessNumberState(difficulty);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Check if game is over
|
|
164
|
+
if (gameState.gameOver) {
|
|
165
|
+
return { display: `This game is over! Start a new game with \`vibe guessnumber @${them}\` (no guess).` };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Make the guess
|
|
169
|
+
const result = guessNumber.makeGuess(gameState, guess);
|
|
170
|
+
if (result.error) {
|
|
171
|
+
return { display: `${result.error}` };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const newGameState = result.gameState;
|
|
175
|
+
const payload = createGamePayload('guessnumber', newGameState);
|
|
176
|
+
|
|
177
|
+
// Send message with game state
|
|
178
|
+
let message = '';
|
|
179
|
+
if (newGameState.won) {
|
|
180
|
+
message = `š You got it! The number was ${newGameState.targetNumber}! Solved in ${newGameState.moves} guesses!`;
|
|
181
|
+
// Post to board
|
|
182
|
+
await postGameResult(myHandle, them, newGameState.moves, newGameState.difficulty);
|
|
183
|
+
} else {
|
|
184
|
+
message = `Guessed ${guess}. ${newGameState.lastHint} Keep trying!`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
await store.sendMessage(myHandle, them, message, 'dm', payload);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
display: formatGameDisplay(newGameState, them)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = { definition, handler };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe hangman ā Play hangman word guessing game
|
|
3
|
+
*
|
|
4
|
+
* Single-player game where you guess letters to find the hidden word
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const config = require('../config');
|
|
8
|
+
const store = require('../store');
|
|
9
|
+
const { createGamePayload } = require('../protocol');
|
|
10
|
+
const { requireInit } = require('./_shared');
|
|
11
|
+
|
|
12
|
+
// Hangman game implementation
|
|
13
|
+
const hangman = require('../games/hangman');
|
|
14
|
+
|
|
15
|
+
const definition = {
|
|
16
|
+
name: 'vibe_hangman',
|
|
17
|
+
description: 'Play hangman - guess letters to find the hidden word',
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
guess: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Letter to guess (a-z) or "new" to start a new game'
|
|
24
|
+
},
|
|
25
|
+
difficulty: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Difficulty level for new games (easy, medium, hard)',
|
|
28
|
+
enum: ['easy', 'medium', 'hard']
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
required: []
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get latest hangman game state for user
|
|
37
|
+
*/
|
|
38
|
+
async function getLatestHangmanState(handle) {
|
|
39
|
+
// Get user's thread with themselves (for single-player games)
|
|
40
|
+
const thread = await store.getThread(handle, handle);
|
|
41
|
+
|
|
42
|
+
// Find the most recent hangman game
|
|
43
|
+
for (let i = thread.length - 1; i >= 0; i--) {
|
|
44
|
+
const msg = thread[i];
|
|
45
|
+
if (msg.payload?.type === 'game' && msg.payload?.game === 'hangman') {
|
|
46
|
+
return msg.payload.state;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function handler(args) {
|
|
53
|
+
const initCheck = requireInit();
|
|
54
|
+
if (initCheck) return initCheck;
|
|
55
|
+
|
|
56
|
+
const { guess, difficulty = 'medium' } = args;
|
|
57
|
+
const myHandle = config.getHandle();
|
|
58
|
+
|
|
59
|
+
// Get current game state
|
|
60
|
+
let gameState = await getLatestHangmanState(myHandle);
|
|
61
|
+
|
|
62
|
+
// Start new game
|
|
63
|
+
if (!gameState || guess === 'new' || (gameState.gameOver && !guess)) {
|
|
64
|
+
gameState = hangman.createInitialHangmanState(difficulty);
|
|
65
|
+
const payload = createGamePayload('hangman', gameState);
|
|
66
|
+
|
|
67
|
+
await store.sendMessage(myHandle, myHandle, `New ${difficulty} hangman game started!`, 'dm', payload);
|
|
68
|
+
|
|
69
|
+
const display = hangman.formatHangmanDisplay(gameState);
|
|
70
|
+
return {
|
|
71
|
+
display: `## New Hangman Game (${difficulty})\n\n${display}\n\nGuess letters with: \`vibe hangman --guess a\``
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Show current game if no guess
|
|
76
|
+
if (!guess) {
|
|
77
|
+
const display = hangman.formatHangmanDisplay(gameState);
|
|
78
|
+
const status = gameState.gameOver ?
|
|
79
|
+
'\nGame over! Use `vibe hangman` with no arguments to start a new game.' :
|
|
80
|
+
'\nGuess letters with: `vibe hangman --guess a`';
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
display: `## Hangman Game\n\n${display}${status}`
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Make a guess
|
|
88
|
+
const result = hangman.makeGuess(gameState, guess);
|
|
89
|
+
|
|
90
|
+
if (result.error) {
|
|
91
|
+
const display = hangman.formatHangmanDisplay(gameState);
|
|
92
|
+
return {
|
|
93
|
+
display: `## Hangman Game\n\n${display}\n\nā **${result.error}**\n\nTry again: \`vibe hangman --guess a\``
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Update game state
|
|
98
|
+
const newGameState = result.gameState;
|
|
99
|
+
const payload = createGamePayload('hangman', newGameState);
|
|
100
|
+
|
|
101
|
+
// Send message with updated state
|
|
102
|
+
let message = `Guessed "${guess.toUpperCase()}".`;
|
|
103
|
+
if (newGameState.gameOver) {
|
|
104
|
+
if (newGameState.won) {
|
|
105
|
+
message += ` š You won! The word was "${newGameState.word.toUpperCase()}".`;
|
|
106
|
+
} else {
|
|
107
|
+
message += ` š Game over! The word was "${newGameState.word.toUpperCase()}".`;
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
if (newGameState.correctGuesses.includes(guess.toLowerCase())) {
|
|
111
|
+
message += ' Good guess! ā
';
|
|
112
|
+
} else {
|
|
113
|
+
message += ' Not in the word! ā';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await store.sendMessage(myHandle, myHandle, message, 'dm', payload);
|
|
118
|
+
|
|
119
|
+
const display = hangman.formatHangmanDisplay(newGameState);
|
|
120
|
+
const status = newGameState.gameOver ?
|
|
121
|
+
'\n\nš® Use `vibe hangman` to start a new game!' :
|
|
122
|
+
'\n\nKeep guessing: `vibe hangman --guess e`';
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
display: `## Hangman Game\n\n${display}${status}`
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = { definition, handler };
|