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,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multiplayer Tic-Tac-Toe — Create or join room-based tic-tac-toe games
|
|
3
|
+
*
|
|
4
|
+
* Room-based system where players can create/join games and play together.
|
|
5
|
+
* Multiple rooms can run simultaneously with spectators allowed!
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const config = require('../config');
|
|
9
|
+
const store = require('../store');
|
|
10
|
+
const { requireInit, normalizeHandle } = require('./_shared');
|
|
11
|
+
const multiTicTacToe = require('../games/multiplayer-tictactoe');
|
|
12
|
+
|
|
13
|
+
const definition = {
|
|
14
|
+
name: 'vibe_multiplayer_tictactoe',
|
|
15
|
+
description: 'Create or join multiplayer tic-tac-toe game rooms. Play with friends and allow spectators!',
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
room: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Room name (creates if doesn\'t exist, e.g., "quick-game", "tournament-finals")'
|
|
22
|
+
},
|
|
23
|
+
action: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Action to take',
|
|
26
|
+
enum: ['view', 'join', 'spectate', 'move', 'leave', 'restart', 'list']
|
|
27
|
+
},
|
|
28
|
+
position: {
|
|
29
|
+
type: 'number',
|
|
30
|
+
description: 'Position to play (1-9)',
|
|
31
|
+
minimum: 1,
|
|
32
|
+
maximum: 9
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
required: ['room']
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Storage key for multiplayer tic-tac-toe rooms
|
|
40
|
+
function getRoomKey(roomName) {
|
|
41
|
+
return `multiplayer_tictactoe:${roomName.toLowerCase()}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Get or create game room
|
|
45
|
+
async function getOrCreateRoom(roomName, hostHandle) {
|
|
46
|
+
const key = getRoomKey(roomName);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const existing = await store.kv.get(key);
|
|
50
|
+
if (existing) {
|
|
51
|
+
return JSON.parse(existing);
|
|
52
|
+
}
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.log(`[multiplayer-tictactoe] Creating new room: ${roomName}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Create new room
|
|
58
|
+
const newRoom = multiTicTacToe.createInitialMultiplayerTicTacToeState(hostHandle, roomName);
|
|
59
|
+
await store.kv.set(key, JSON.stringify(newRoom));
|
|
60
|
+
return newRoom;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Save room state
|
|
64
|
+
async function saveRoom(roomName, gameState) {
|
|
65
|
+
const key = getRoomKey(roomName);
|
|
66
|
+
await store.kv.set(key, JSON.stringify(gameState));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// List all active rooms
|
|
70
|
+
async function listActiveRooms() {
|
|
71
|
+
try {
|
|
72
|
+
// In a real implementation, we'd query the KV store for all multiplayer_tictactoe:* keys
|
|
73
|
+
// For now, return empty array since we can't easily list KV keys
|
|
74
|
+
return [];
|
|
75
|
+
} catch (e) {
|
|
76
|
+
console.error('[multiplayer-tictactoe] Error listing rooms:', e.message);
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Post game activity to board
|
|
82
|
+
async function postGameActivity(roomName, playerHandle, action, details = '') {
|
|
83
|
+
const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
let content = '';
|
|
87
|
+
switch (action) {
|
|
88
|
+
case 'created':
|
|
89
|
+
content = `🎯 @${playerHandle} created tic-tac-toe room "${roomName}" — join the game!`;
|
|
90
|
+
break;
|
|
91
|
+
case 'joined':
|
|
92
|
+
content = `🎯 @${playerHandle} joined tic-tac-toe room "${roomName}"`;
|
|
93
|
+
break;
|
|
94
|
+
case 'started':
|
|
95
|
+
content = `🎯 Tic-tac-toe game started in room "${roomName}" — spectators welcome!`;
|
|
96
|
+
break;
|
|
97
|
+
case 'won':
|
|
98
|
+
content = `🎉 @${playerHandle} won the tic-tac-toe game in "${roomName}"!`;
|
|
99
|
+
break;
|
|
100
|
+
case 'draw':
|
|
101
|
+
content = `🤝 Tic-tac-toe game in "${roomName}" ended in a draw!`;
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
return; // Don't post for other actions
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
await fetch(`${API_URL}/api/board`, {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: { 'Content-Type': 'application/json' },
|
|
110
|
+
body: JSON.stringify({
|
|
111
|
+
author: 'echo',
|
|
112
|
+
content,
|
|
113
|
+
category: 'general'
|
|
114
|
+
})
|
|
115
|
+
});
|
|
116
|
+
} catch (e) {
|
|
117
|
+
console.error('[multiplayer-tictactoe] Failed to post to board:', e.message);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function handler(args) {
|
|
122
|
+
const initCheck = requireInit();
|
|
123
|
+
if (initCheck) return initCheck;
|
|
124
|
+
|
|
125
|
+
const { room, action = 'view', position } = args;
|
|
126
|
+
const myHandle = config.getHandle();
|
|
127
|
+
|
|
128
|
+
// List all active rooms
|
|
129
|
+
if (action === 'list') {
|
|
130
|
+
const rooms = await listActiveRooms();
|
|
131
|
+
|
|
132
|
+
if (rooms.length === 0) {
|
|
133
|
+
return {
|
|
134
|
+
display: `## 🎯 Multiplayer Tic-Tac-Toe Rooms\n\nNo active rooms found.\n\nCreate one: \`vibe multiplayer-tictactoe --room "my-game" --action join\``
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let display = `## 🎯 Active Multiplayer Tic-Tac-Toe Rooms\n\n`;
|
|
139
|
+
|
|
140
|
+
for (const roomData of rooms) {
|
|
141
|
+
const gameState = roomData.state;
|
|
142
|
+
const players = gameState.players || [];
|
|
143
|
+
const spectators = gameState.spectators || [];
|
|
144
|
+
const status = gameState.gameOver ? '✅ Complete' :
|
|
145
|
+
gameState.gameStarted ? '🎲 In Progress' :
|
|
146
|
+
`🏁 Waiting (${players.length}/2 players)`;
|
|
147
|
+
|
|
148
|
+
display += `**${roomData.name}** - ${status}\n`;
|
|
149
|
+
display += ` Host: @${gameState.host}\n`;
|
|
150
|
+
display += ` Players: ${players.map(p => `@${p}`).join(', ') || 'None'}\n`;
|
|
151
|
+
if (spectators.length > 0) {
|
|
152
|
+
display += ` Spectators: ${spectators.map(s => `@${s}`).join(', ')}\n`;
|
|
153
|
+
}
|
|
154
|
+
display += ` Join: \`vibe multiplayer-tictactoe --room "${roomData.name}" --action join\`\n\n`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { display };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!room) {
|
|
161
|
+
return { display: 'Room name is required. Use something like "quick-game" or "tournament-finals"' };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Normalize room name
|
|
165
|
+
const roomName = room.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
// Get or create room
|
|
169
|
+
let gameState = await getOrCreateRoom(roomName, myHandle);
|
|
170
|
+
let wasNewRoom = gameState.players.length === 0 && gameState.spectators.length === 0;
|
|
171
|
+
let wasWaitingForPlayers = !gameState.gameStarted && gameState.players.length < 2;
|
|
172
|
+
|
|
173
|
+
// Handle different actions
|
|
174
|
+
switch (action) {
|
|
175
|
+
case 'view':
|
|
176
|
+
// Just show current state
|
|
177
|
+
break;
|
|
178
|
+
|
|
179
|
+
case 'join':
|
|
180
|
+
const joinResult = multiTicTacToe.joinRoom(gameState, myHandle, false);
|
|
181
|
+
if (joinResult.error) {
|
|
182
|
+
return { display: joinResult.error };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
gameState = joinResult.gameState;
|
|
186
|
+
await saveRoom(roomName, gameState);
|
|
187
|
+
|
|
188
|
+
// Post activity based on what happened
|
|
189
|
+
if (wasNewRoom) {
|
|
190
|
+
await postGameActivity(roomName, myHandle, 'created');
|
|
191
|
+
} else if (wasWaitingForPlayers) {
|
|
192
|
+
await postGameActivity(roomName, myHandle, 'joined');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// If game just started, announce it
|
|
196
|
+
if (gameState.gameStarted && wasWaitingForPlayers) {
|
|
197
|
+
await postGameActivity(roomName, null, 'started');
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
|
|
201
|
+
case 'spectate':
|
|
202
|
+
const spectateResult = multiTicTacToe.joinRoom(gameState, myHandle, true);
|
|
203
|
+
if (spectateResult.error) {
|
|
204
|
+
return { display: spectateResult.error };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
gameState = spectateResult.gameState;
|
|
208
|
+
await saveRoom(roomName, gameState);
|
|
209
|
+
break;
|
|
210
|
+
|
|
211
|
+
case 'move':
|
|
212
|
+
if (position === undefined) {
|
|
213
|
+
return { display: 'Move action requires position. Example: `--action move --position 5`' };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const moveResult = multiTicTacToe.makeMove(gameState, position, myHandle);
|
|
217
|
+
if (moveResult.error) {
|
|
218
|
+
return { display: moveResult.error };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const prevGameState = gameState;
|
|
222
|
+
gameState = moveResult.gameState;
|
|
223
|
+
await saveRoom(roomName, gameState);
|
|
224
|
+
|
|
225
|
+
// Post result if game ended
|
|
226
|
+
if (gameState.gameOver && !prevGameState.gameOver) {
|
|
227
|
+
if (gameState.winner) {
|
|
228
|
+
const winnerHandle = Object.keys(gameState.playerSymbols).find(h => gameState.playerSymbols[h] === gameState.winner);
|
|
229
|
+
await postGameActivity(roomName, winnerHandle, 'won');
|
|
230
|
+
} else if (gameState.isDraw) {
|
|
231
|
+
await postGameActivity(roomName, null, 'draw');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
|
|
236
|
+
case 'leave':
|
|
237
|
+
const leaveResult = multiTicTacToe.leaveRoom(gameState, myHandle);
|
|
238
|
+
if (leaveResult.error) {
|
|
239
|
+
return { display: leaveResult.error };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
gameState = leaveResult.gameState;
|
|
243
|
+
await saveRoom(roomName, gameState);
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
display: `✅ Left room "${roomName}". ${gameState.gameOver ? 'Game ended due to forfeit.' : ''}`
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
case 'restart':
|
|
250
|
+
const restartResult = multiTicTacToe.restartGame(gameState, myHandle);
|
|
251
|
+
if (restartResult.error) {
|
|
252
|
+
return { display: restartResult.error };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
gameState = restartResult.gameState;
|
|
256
|
+
await saveRoom(roomName, gameState);
|
|
257
|
+
break;
|
|
258
|
+
|
|
259
|
+
default:
|
|
260
|
+
return { display: `Unknown action "${action}". Use: view, join, spectate, move, leave, restart, or list` };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Format and return display
|
|
264
|
+
const display = multiTicTacToe.formatMultiplayerTicTacToeDisplay(gameState, myHandle);
|
|
265
|
+
let result = `# 🎯 Multiplayer Tic-Tac-Toe Room: "${roomName}"\n\n${display}`;
|
|
266
|
+
|
|
267
|
+
// Add usage instructions based on game state
|
|
268
|
+
if (!gameState.gameStarted) {
|
|
269
|
+
result += '\n**How to play:**\n';
|
|
270
|
+
if (gameState.players.length < 2) {
|
|
271
|
+
result += `• \`vibe multiplayer-tictactoe --room "${roomName}" --action join\` - Join as player\n`;
|
|
272
|
+
}
|
|
273
|
+
result += `• \`vibe multiplayer-tictactoe --room "${roomName}" --action spectate\` - Watch the game\n`;
|
|
274
|
+
} else if (!gameState.gameOver) {
|
|
275
|
+
result += '\n**Game commands:**\n';
|
|
276
|
+
result += `• \`vibe multiplayer-tictactoe --room "${roomName}" --action move --position N\` - Play position 1-9\n`;
|
|
277
|
+
result += `• \`vibe multiplayer-tictactoe --room "${roomName}" --action leave\` - Leave game\n`;
|
|
278
|
+
} else {
|
|
279
|
+
result += '\n**Game over!**\n';
|
|
280
|
+
result += `• \`vibe multiplayer-tictactoe --room "${roomName}" --action restart\` - Play again (players only)\n`;
|
|
281
|
+
result += `• \`vibe multiplayer-tictactoe --room "${roomName}" --action leave\` - Leave room\n`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Add room sharing info
|
|
285
|
+
result += `\n**Share this room:** \`vibe multiplayer-tictactoe --room "${roomName}"\`\n`;
|
|
286
|
+
|
|
287
|
+
// Show available positions if game is active
|
|
288
|
+
if (gameState.gameStarted && !gameState.gameOver) {
|
|
289
|
+
const available = multiTicTacToe.getAvailablePositions(gameState);
|
|
290
|
+
if (available.length > 0) {
|
|
291
|
+
result += `**Available positions:** ${available.join(', ')}\n`;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return { display: result };
|
|
296
|
+
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.error('[multiplayer-tictactoe] Error:', error);
|
|
299
|
+
return { display: `Error: ${error.message}` };
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
module.exports = { definition, handler };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe recall — Query thread memories
|
|
3
|
+
*
|
|
4
|
+
* Shows observations saved about a specific thread.
|
|
5
|
+
* Local, inspectable, searchable.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* vibe recall @handle — Show memories about @handle
|
|
9
|
+
* vibe recall — Show all threads with memories
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const config = require('../config');
|
|
13
|
+
const memory = require('../memory');
|
|
14
|
+
|
|
15
|
+
const definition = {
|
|
16
|
+
name: 'vibe_recall',
|
|
17
|
+
description: 'Query thread memories. Shows saved observations about a person or lists all threads.',
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
handle: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Who to recall memories about (e.g., @alex). If omitted, lists all threads.'
|
|
24
|
+
},
|
|
25
|
+
limit: {
|
|
26
|
+
type: 'number',
|
|
27
|
+
description: 'Maximum memories to show (default: 10)'
|
|
28
|
+
},
|
|
29
|
+
search: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'Optional search term to filter memories'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
async function handler(args) {
|
|
38
|
+
if (!config.isInitialized()) {
|
|
39
|
+
return {
|
|
40
|
+
display: 'Run `vibe init` first to set your identity.'
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let { handle, limit = 10, search } = args;
|
|
45
|
+
|
|
46
|
+
// If no handle, list all threads
|
|
47
|
+
if (!handle) {
|
|
48
|
+
const threads = memory.listThreads();
|
|
49
|
+
|
|
50
|
+
if (threads.length === 0) {
|
|
51
|
+
let output = `## Memory — Empty\n\n`;
|
|
52
|
+
output += `_No memories saved yet._\n\n`;
|
|
53
|
+
output += `Save one with: \`vibe remember @handle "observation"\`\n`;
|
|
54
|
+
output += `Memories are stored locally: \`${memory.getMemoryPath()}/\``;
|
|
55
|
+
return { display: output };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let output = `## Memory — ${threads.length} ${threads.length === 1 ? 'Thread' : 'Threads'}\n\n`;
|
|
59
|
+
|
|
60
|
+
for (const thread of threads) {
|
|
61
|
+
const age = formatAge(thread.newestMemory);
|
|
62
|
+
output += `**@${thread.handle}** — ${thread.count} ${thread.count === 1 ? 'memory' : 'memories'} (${age})\n`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
output += `\n---\n`;
|
|
66
|
+
output += `_View a thread: \`vibe recall @handle\`_\n`;
|
|
67
|
+
output += `_Storage: \`${memory.getMemoryPath()}/\`_`;
|
|
68
|
+
|
|
69
|
+
return { display: output };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Clean handle
|
|
73
|
+
handle = handle.replace(/^@/, '').toLowerCase();
|
|
74
|
+
|
|
75
|
+
// Get memories for this thread
|
|
76
|
+
let memories = memory.recall(handle, limit);
|
|
77
|
+
|
|
78
|
+
if (memories.length === 0) {
|
|
79
|
+
let output = `## Memory — @${handle}\n\n`;
|
|
80
|
+
output += `_No memories saved about @${handle}._\n\n`;
|
|
81
|
+
output += `Save one with: \`vibe remember @${handle} "observation"\``;
|
|
82
|
+
return { display: output };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Apply search filter if provided
|
|
86
|
+
if (search) {
|
|
87
|
+
const searchLower = search.toLowerCase();
|
|
88
|
+
memories = memories.filter(m =>
|
|
89
|
+
m.observation.toLowerCase().includes(searchLower)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (memories.length === 0) {
|
|
93
|
+
return {
|
|
94
|
+
display: `## Memory — @${handle}\n\n_No memories matching "${search}"._`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Format output
|
|
100
|
+
let output = `## Memory — @${handle}\n\n`;
|
|
101
|
+
|
|
102
|
+
for (const m of memories) {
|
|
103
|
+
const age = formatAge(m.timestamp);
|
|
104
|
+
output += `• "${m.observation}" — _${age}_\n`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const total = memory.count(handle);
|
|
108
|
+
if (total > memories.length) {
|
|
109
|
+
output += `\n_Showing ${memories.length} of ${total}. Use \`limit\` for more._\n`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
output += `\n---\n`;
|
|
113
|
+
output += `_Add: \`vibe remember @${handle} "..."\`_\n`;
|
|
114
|
+
output += `_Delete: \`vibe forget @${handle}\`_\n`;
|
|
115
|
+
output += `_File: \`${memory.getThreadFile(handle)}\`_`;
|
|
116
|
+
|
|
117
|
+
return { display: output };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Format a timestamp as relative age
|
|
122
|
+
*/
|
|
123
|
+
function formatAge(timestamp) {
|
|
124
|
+
if (!timestamp) return 'unknown';
|
|
125
|
+
|
|
126
|
+
const now = Date.now();
|
|
127
|
+
const then = new Date(timestamp).getTime();
|
|
128
|
+
|
|
129
|
+
if (isNaN(then)) return 'unknown';
|
|
130
|
+
|
|
131
|
+
const diff = now - then;
|
|
132
|
+
|
|
133
|
+
const minutes = Math.floor(diff / (1000 * 60));
|
|
134
|
+
const hours = Math.floor(diff / (1000 * 60 * 60));
|
|
135
|
+
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
136
|
+
|
|
137
|
+
if (minutes < 1) return 'just now';
|
|
138
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
139
|
+
if (hours < 24) return `${hours}h ago`;
|
|
140
|
+
if (days === 1) return 'yesterday';
|
|
141
|
+
if (days < 7) return `${days}d ago`;
|
|
142
|
+
if (days < 30) return `${Math.floor(days / 7)}w ago`;
|
|
143
|
+
|
|
144
|
+
return new Date(timestamp).toLocaleDateString();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = { definition, handler };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe remember — Save observation to thread memory
|
|
3
|
+
*
|
|
4
|
+
* Memory is a promotion, not a capture.
|
|
5
|
+
* Explicit consent. Thread-scoped. Append-only.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* vibe remember @handle "Solienne prefers center opening"
|
|
9
|
+
* vibe remember "We discussed OAuth implementation" (uses last DM thread)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const config = require('../config');
|
|
13
|
+
const memory = require('../memory');
|
|
14
|
+
const store = require('../store');
|
|
15
|
+
|
|
16
|
+
const definition = {
|
|
17
|
+
name: 'vibe_remember',
|
|
18
|
+
description: 'Save an observation to thread memory. Explicit, local, inspectable.',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
observation: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'The observation to remember (required)'
|
|
25
|
+
},
|
|
26
|
+
handle: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'Who this memory is about (e.g., @alex). If omitted, uses last active thread.'
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
required: ['observation']
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
async function handler(args) {
|
|
36
|
+
if (!config.isInitialized()) {
|
|
37
|
+
return {
|
|
38
|
+
display: 'Run `vibe init` first to set your identity.'
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { observation } = args;
|
|
43
|
+
let { handle } = args;
|
|
44
|
+
|
|
45
|
+
// Validate observation
|
|
46
|
+
if (!observation || observation.trim().length === 0) {
|
|
47
|
+
return {
|
|
48
|
+
display: 'Usage: `vibe remember "observation"` or `vibe remember @handle "observation"`'
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Clean handle
|
|
53
|
+
if (handle) {
|
|
54
|
+
handle = handle.replace(/^@/, '').toLowerCase();
|
|
55
|
+
} else {
|
|
56
|
+
// Try to find last active thread
|
|
57
|
+
const myHandle = config.getHandle();
|
|
58
|
+
const inbox = await store.getInbox(myHandle);
|
|
59
|
+
|
|
60
|
+
if (inbox && inbox.length > 0) {
|
|
61
|
+
// Use most recent thread
|
|
62
|
+
handle = inbox[0].handle;
|
|
63
|
+
} else {
|
|
64
|
+
return {
|
|
65
|
+
display: '**No thread context.** Use `vibe remember @handle "observation"` to specify who this memory is about.'
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Save the memory
|
|
71
|
+
const saved = memory.remember(handle, observation.trim());
|
|
72
|
+
const count = memory.count(handle);
|
|
73
|
+
|
|
74
|
+
// Format confirmation
|
|
75
|
+
let output = `## Memory Saved\n\n`;
|
|
76
|
+
output += `**About:** @${handle}\n`;
|
|
77
|
+
output += `**Observation:** "${saved.observation}"\n`;
|
|
78
|
+
output += `**Time:** ${new Date(saved.timestamp).toLocaleString()}\n\n`;
|
|
79
|
+
output += `---\n`;
|
|
80
|
+
output += `_Thread now has ${count} ${count === 1 ? 'memory' : 'memories'}. View with \`vibe recall @${handle}\`_\n`;
|
|
81
|
+
output += `_Inspect: \`${memory.getThreadFile(handle)}\`_`;
|
|
82
|
+
|
|
83
|
+
return { display: output };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { definition, handler };
|