slashvibe-mcp 0.2.8 → 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 +41 -58
- 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 +27 -15
- 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 +77 -53
- package/intelligence/index.js +9 -2
- package/intelligence/interests.js +369 -0
- package/notification-emitter.js +77 -0
- package/notify.js +5 -1
- package/package.json +18 -6
- package/prompts.js +1 -1
- package/protocol/index.js +73 -0
- package/setup.js +402 -0
- package/store/api.js +436 -211
- package/store/profiles.js +160 -12
- package/tools/_actions.js +362 -21
- package/tools/_discovery.js +119 -26
- 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/analytics.js +191 -0
- package/tools/approve.js +197 -0
- package/tools/artifact-create.js +14 -3
- package/tools/artifacts-price.js +107 -0
- package/tools/available.js +120 -0
- package/tools/broadcast.js +286 -0
- package/tools/chat.js +202 -0
- package/tools/collaborative-drawing.js +1 -1
- package/tools/connection-status.js +178 -0
- package/tools/discover.js +350 -34
- package/tools/dm.js +80 -8
- package/tools/earnings.js +126 -0
- package/tools/feed.js +35 -4
- package/tools/follow.js +224 -0
- package/tools/friends.js +207 -0
- package/tools/gig-browse.js +206 -0
- package/tools/gig-complete.js +144 -0
- package/tools/help.js +3 -3
- package/tools/idea.js +9 -2
- package/tools/inbox.js +289 -105
- package/tools/init.js +106 -27
- package/tools/invite.js +15 -4
- package/tools/migrate.js +3 -3
- package/tools/multiplayer-game.js +1 -1
- package/tools/onboarding.js +7 -7
- package/tools/open.js +143 -12
- package/tools/party-game.js +1 -1
- package/tools/plan.js +225 -0
- package/tools/proof-of-work.js +144 -0
- package/tools/reply.js +166 -0
- package/tools/report.js +1 -1
- 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 +90 -2
- package/tools/ship.js +30 -7
- package/tools/smart-check.js +201 -0
- package/tools/start.js +147 -12
- 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 +1 -1
- package/tools/tip.js +150 -77
- package/tools/token.js +4 -4
- package/tools/update.js +1 -1
- package/tools/wallet.js +221 -79
- package/tools/watch.js +157 -0
- package/tools/who.js +30 -1
- package/tools/withdraw.js +145 -0
- package/tools/work-summary.js +96 -0
- package/version.json +10 -8
- package/LICENSE +0 -21
- package/store/sqlite.js +0 -347
- /package/tools/{auto-suggest-connections.js → _deprecated/auto-suggest-connections.js} +0 -0
- /package/tools/{away.js → _deprecated/away.js} +0 -0
- /package/tools/{back.js → _deprecated/back.js} +0 -0
- /package/tools/{bootstrap-skills.js → _deprecated/bootstrap-skills.js} +0 -0
- /package/tools/{bridge-dashboard.js → _deprecated/bridge-dashboard.js} +0 -0
- /package/tools/{bridge-health.js → _deprecated/bridge-health.js} +0 -0
- /package/tools/{bridge-live.js → _deprecated/bridge-live.js} +0 -0
- /package/tools/{bridges.js → _deprecated/bridges.js} +0 -0
- /package/tools/{colorguess.js → _deprecated/colorguess.js} +0 -0
- /package/tools/{discover-insights.js → _deprecated/discover-insights.js} +0 -0
- /package/tools/{discover-momentum.js → _deprecated/discover-momentum.js} +0 -0
- /package/tools/{discovery-analytics.js → _deprecated/discovery-analytics.js} +0 -0
- /package/tools/{discovery-auto-suggest.js → _deprecated/discovery-auto-suggest.js} +0 -0
- /package/tools/{discovery-bootstrap.js → _deprecated/discovery-bootstrap.js} +0 -0
- /package/tools/{discovery-daily.js → _deprecated/discovery-daily.js} +0 -0
- /package/tools/{discovery-dashboard.js → _deprecated/discovery-dashboard.js} +0 -0
- /package/tools/{discovery-digest.js → _deprecated/discovery-digest.js} +0 -0
- /package/tools/{discovery-hub.js → _deprecated/discovery-hub.js} +0 -0
- /package/tools/{discovery-insights.js → _deprecated/discovery-insights.js} +0 -0
- /package/tools/{discovery-momentum.js → _deprecated/discovery-momentum.js} +0 -0
- /package/tools/{discovery-monitor.js → _deprecated/discovery-monitor.js} +0 -0
- /package/tools/{discovery-proactive.js → _deprecated/discovery-proactive.js} +0 -0
- /package/tools/{draw.js → _deprecated/draw.js} +0 -0
- /package/tools/{farcaster.js → _deprecated/farcaster.js} +0 -0
- /package/tools/{forget.js → _deprecated/forget.js} +0 -0
- /package/tools/{games-catalog.js → _deprecated/games-catalog.js} +0 -0
- /package/tools/{games.js → _deprecated/games.js} +0 -0
- /package/tools/{guessnumber.js → _deprecated/guessnumber.js} +0 -0
- /package/tools/{hangman.js → _deprecated/hangman.js} +0 -0
- /package/tools/{multiplayer-tictactoe.js → _deprecated/multiplayer-tictactoe.js} +0 -0
- /package/tools/{mute.js → _deprecated/mute.js} +0 -0
- /package/tools/{recall.js → _deprecated/recall.js} +0 -0
- /package/tools/{remember.js → _deprecated/remember.js} +0 -0
- /package/tools/{riddle.js → _deprecated/riddle.js} +0 -0
- /package/tools/{run-bootstrap.js → _deprecated/run-bootstrap.js} +0 -0
- /package/tools/{skills-analytics.js → _deprecated/skills-analytics.js} +0 -0
- /package/tools/{skills-bootstrap.js → _deprecated/skills-bootstrap.js} +0 -0
- /package/tools/{skills-dashboard.js → _deprecated/skills-dashboard.js} +0 -0
- /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
- /package/tools/{skills.js → _deprecated/skills.js} +0 -0
- /package/tools/{smart-intro.js → _deprecated/smart-intro.js} +0 -0
- /package/tools/{storybuilder.js → _deprecated/storybuilder.js} +0 -0
- /package/tools/{telegram-bot.js → _deprecated/telegram-bot.js} +0 -0
- /package/tools/{telegram-setup.js → _deprecated/telegram-setup.js} +0 -0
- /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
- /package/tools/{twentyquestions.js → _deprecated/twentyquestions.js} +0 -0
- /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Word Association game implementation for /vibe
|
|
3
|
+
* Players take turns saying words that associate with the previous word
|
|
4
|
+
* Build creative chains of connected ideas!
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Simple word validation
|
|
8
|
+
function isValidWord(word) {
|
|
9
|
+
// Basic checks: alphabetic, reasonable length, not too short
|
|
10
|
+
if (!/^[a-zA-Z\-']+$/.test(word)) return false; // Allow hyphens and apostrophes
|
|
11
|
+
if (word.length < 2) return false;
|
|
12
|
+
if (word.length > 30) return false;
|
|
13
|
+
|
|
14
|
+
// Allow single meaningful words
|
|
15
|
+
if (word.length === 1) {
|
|
16
|
+
return ['a', 'i'].includes(word.toLowerCase());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Create initial word association state
|
|
23
|
+
function createInitialWordAssociationState() {
|
|
24
|
+
return {
|
|
25
|
+
words: [],
|
|
26
|
+
players: [],
|
|
27
|
+
currentPlayer: null,
|
|
28
|
+
moves: 0,
|
|
29
|
+
gameOver: false,
|
|
30
|
+
winner: null,
|
|
31
|
+
lastWord: null,
|
|
32
|
+
usedWords: new Set(), // Track words to prevent exact repeats
|
|
33
|
+
playerTurnIndex: 0,
|
|
34
|
+
maxWords: 30, // End game after 30 words for now
|
|
35
|
+
associations: [], // Store word -> word pairs for fun stats
|
|
36
|
+
startedAt: new Date().toISOString()
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Add player to game
|
|
41
|
+
function addPlayer(gameState, playerHandle) {
|
|
42
|
+
if (gameState.players.includes(playerHandle)) {
|
|
43
|
+
return { error: 'Player already in the game' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (gameState.players.length >= 6) {
|
|
47
|
+
return { error: 'Game is full (max 6 players)' };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const newPlayers = [...gameState.players, playerHandle];
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
success: true,
|
|
54
|
+
gameState: {
|
|
55
|
+
...gameState,
|
|
56
|
+
players: newPlayers,
|
|
57
|
+
currentPlayer: gameState.currentPlayer || playerHandle // First player starts
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Make a word association move
|
|
63
|
+
function makeMove(gameState, word, playerHandle) {
|
|
64
|
+
const { words, players, currentPlayer, moves, usedWords, lastWord, maxWords } = gameState;
|
|
65
|
+
|
|
66
|
+
// Normalize word
|
|
67
|
+
const normalizedWord = word.toLowerCase().trim().replace(/['"]/g, '');
|
|
68
|
+
|
|
69
|
+
// Validate basic word format
|
|
70
|
+
if (!isValidWord(normalizedWord)) {
|
|
71
|
+
return { error: 'Invalid word format. Use only letters (2-30 characters).' };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check if it's the right player's turn (if multiplayer)
|
|
75
|
+
if (players.length > 1 && currentPlayer !== playerHandle) {
|
|
76
|
+
return { error: `Not your turn! Waiting for @${currentPlayer}` };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check if word was already used (allow some flexibility)
|
|
80
|
+
if (usedWords.has(normalizedWord)) {
|
|
81
|
+
return { error: 'Word already used! Try a different word.' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check if it's the same word as last word
|
|
85
|
+
if (lastWord && normalizedWord === lastWord.toLowerCase()) {
|
|
86
|
+
return { error: 'Cannot repeat the same word immediately!' };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Valid move - update game state
|
|
90
|
+
const newWords = [...words, { word: normalizedWord, player: playerHandle, timestamp: new Date().toISOString() }];
|
|
91
|
+
const newUsedWords = new Set([...usedWords, normalizedWord]);
|
|
92
|
+
const newAssociations = lastWord ? [...gameState.associations, `${lastWord} → ${normalizedWord}`] : gameState.associations;
|
|
93
|
+
|
|
94
|
+
// Determine next player
|
|
95
|
+
let nextPlayerIndex = gameState.playerTurnIndex;
|
|
96
|
+
let nextPlayer = currentPlayer;
|
|
97
|
+
|
|
98
|
+
if (players.length > 1) {
|
|
99
|
+
nextPlayerIndex = (gameState.playerTurnIndex + 1) % players.length;
|
|
100
|
+
nextPlayer = players[nextPlayerIndex];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check if game should end
|
|
104
|
+
const shouldEnd = moves + 1 >= maxWords;
|
|
105
|
+
|
|
106
|
+
const newGameState = {
|
|
107
|
+
...gameState,
|
|
108
|
+
words: newWords,
|
|
109
|
+
currentPlayer: shouldEnd ? null : nextPlayer,
|
|
110
|
+
playerTurnIndex: nextPlayerIndex,
|
|
111
|
+
moves: moves + 1,
|
|
112
|
+
gameOver: shouldEnd,
|
|
113
|
+
winner: shouldEnd ? 'everyone' : null,
|
|
114
|
+
lastWord: normalizedWord,
|
|
115
|
+
usedWords: newUsedWords,
|
|
116
|
+
associations: newAssociations
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return { success: true, gameState: newGameState };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Format word association for display
|
|
123
|
+
function formatWordAssociationDisplay(gameState) {
|
|
124
|
+
const { words, players, currentPlayer, moves, gameOver, maxWords, associations } = gameState;
|
|
125
|
+
|
|
126
|
+
let display = `🧠 **Word Association** (${moves}/${maxWords} words)\n\n`;
|
|
127
|
+
|
|
128
|
+
if (gameOver) {
|
|
129
|
+
display += '🎉 **Game Complete!** What a creative journey!\n\n';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (words.length === 0) {
|
|
133
|
+
if (players.length === 0) {
|
|
134
|
+
display += '**Start with any word!**\n\n';
|
|
135
|
+
display += 'Word association is about connecting ideas. Each new word should relate to the previous one.\n';
|
|
136
|
+
} else {
|
|
137
|
+
display += `**@${currentPlayer}, start us off with any word!**\n\n`;
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
// Show the association chain (last 8 words to keep it readable)
|
|
141
|
+
const recentWords = words.slice(-8);
|
|
142
|
+
const wordChain = recentWords.map(w => {
|
|
143
|
+
const capitalizedWord = w.word.charAt(0).toUpperCase() + w.word.slice(1);
|
|
144
|
+
return `**${capitalizedWord}** (@${w.player})`;
|
|
145
|
+
}).join(' → ');
|
|
146
|
+
|
|
147
|
+
display += '**Chain:** ' + wordChain;
|
|
148
|
+
|
|
149
|
+
if (words.length > 8) {
|
|
150
|
+
display += ` (showing last 8 of ${words.length})`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
display += '\n\n';
|
|
154
|
+
|
|
155
|
+
if (!gameOver) {
|
|
156
|
+
const lastWordEntry = words[words.length - 1];
|
|
157
|
+
const lastWord = lastWordEntry.word;
|
|
158
|
+
display += `**"${lastWord.charAt(0).toUpperCase() + lastWord.slice(1)}" makes you think of...**\n\n`;
|
|
159
|
+
|
|
160
|
+
if (players.length > 1) {
|
|
161
|
+
display += `Turn: **@${currentPlayer}**`;
|
|
162
|
+
} else {
|
|
163
|
+
display += '**Your turn!** What word comes to mind?';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Show players if multiplayer
|
|
169
|
+
if (players.length > 1) {
|
|
170
|
+
display += `\n\n**Players (${players.length}):** ${players.map(p => `@${p}`).join(', ')}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Show some fun stats at the end
|
|
174
|
+
if (gameOver && associations.length > 0) {
|
|
175
|
+
display += '\n\n**Creative Journey:**\n';
|
|
176
|
+
// Show the full chain condensed
|
|
177
|
+
const fullChain = words.map(w => w.word.charAt(0).toUpperCase() + w.word.slice(1)).join(' → ');
|
|
178
|
+
display += fullChain;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return display;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Get game stats for fun
|
|
185
|
+
function getGameStats(gameState) {
|
|
186
|
+
const { words, players, associations } = gameState;
|
|
187
|
+
|
|
188
|
+
if (words.length === 0) return null;
|
|
189
|
+
|
|
190
|
+
// Player contribution stats
|
|
191
|
+
const contributions = {};
|
|
192
|
+
words.forEach(w => {
|
|
193
|
+
contributions[w.player] = (contributions[w.player] || 0) + 1;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
totalWords: words.length,
|
|
198
|
+
contributors: Object.keys(contributions).length,
|
|
199
|
+
contributions,
|
|
200
|
+
longestWord: Math.max(...words.map(w => w.word.length)),
|
|
201
|
+
shortestWord: Math.min(...words.map(w => w.word.length)),
|
|
202
|
+
uniqueWords: new Set(words.map(w => w.word)).size
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Check for interesting patterns or themes
|
|
207
|
+
function findThemes(gameState) {
|
|
208
|
+
const { words } = gameState;
|
|
209
|
+
|
|
210
|
+
if (words.length < 3) return [];
|
|
211
|
+
|
|
212
|
+
const themes = [];
|
|
213
|
+
const wordList = words.map(w => w.word.toLowerCase());
|
|
214
|
+
|
|
215
|
+
// Look for color words
|
|
216
|
+
const colors = ['red', 'blue', 'green', 'yellow', 'orange', 'purple', 'pink', 'black', 'white', 'brown', 'gray', 'grey'];
|
|
217
|
+
const colorWords = wordList.filter(w => colors.includes(w));
|
|
218
|
+
if (colorWords.length >= 2) {
|
|
219
|
+
themes.push(`🌈 Colors: ${colorWords.join(', ')}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Look for animal words
|
|
223
|
+
const animals = ['cat', 'dog', 'bird', 'fish', 'mouse', 'lion', 'tiger', 'bear', 'wolf', 'fox', 'rabbit', 'horse', 'cow', 'pig', 'sheep'];
|
|
224
|
+
const animalWords = wordList.filter(w => animals.some(animal => w.includes(animal) || animal.includes(w)));
|
|
225
|
+
if (animalWords.length >= 2) {
|
|
226
|
+
themes.push(`🐾 Animals: ${animalWords.join(', ')}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Look for nature words
|
|
230
|
+
const nature = ['tree', 'flower', 'water', 'sun', 'moon', 'star', 'cloud', 'rain', 'snow', 'mountain', 'ocean', 'forest'];
|
|
231
|
+
const natureWords = wordList.filter(w => nature.some(n => w.includes(n) || n.includes(w)));
|
|
232
|
+
if (natureWords.length >= 2) {
|
|
233
|
+
themes.push(`🌿 Nature: ${natureWords.join(', ')}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return themes;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = {
|
|
240
|
+
createInitialWordAssociationState,
|
|
241
|
+
addPlayer,
|
|
242
|
+
makeMove,
|
|
243
|
+
formatWordAssociationDisplay,
|
|
244
|
+
isValidWord,
|
|
245
|
+
getGameStats,
|
|
246
|
+
findThemes
|
|
247
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Word Chain game implementation for /vibe
|
|
3
|
+
* Players take turns saying words that start with the last letter of the previous word
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Simple word validation - checks if it's a reasonable English word
|
|
7
|
+
// In a full implementation, this could use a proper dictionary API
|
|
8
|
+
function isValidWord(word) {
|
|
9
|
+
// Basic checks: alphabetic, reasonable length, not too short
|
|
10
|
+
if (!/^[a-zA-Z]+$/.test(word)) return false;
|
|
11
|
+
if (word.length < 2) return false;
|
|
12
|
+
if (word.length > 20) return false;
|
|
13
|
+
|
|
14
|
+
// Some very basic filtering of common valid words vs nonsense
|
|
15
|
+
// This is a simplified check - a real game would use a dictionary
|
|
16
|
+
const commonPrefixes = ['th', 'he', 'in', 'er', 'an', 're', 'ed', 'nd', 'ou', 'ea', 'ti', 'to', 'it', 'st', 'io', 'le', 'is', 'on', 'ur', 'ar', 'nt', 'al'];
|
|
17
|
+
const commonSuffixes = ['ed', 'er', 'ing', 'ly', 'est', 'ion', 'tion', 'sion', 'ness', 'ment', 'able', 'ible'];
|
|
18
|
+
|
|
19
|
+
const lowerWord = word.toLowerCase();
|
|
20
|
+
|
|
21
|
+
// Allow single letters for now (like 'a', 'i')
|
|
22
|
+
if (word.length === 1) {
|
|
23
|
+
return ['a', 'i'].includes(lowerWord);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check for common patterns that suggest it's a real word
|
|
27
|
+
const hasCommonPrefix = commonPrefixes.some(prefix => lowerWord.startsWith(prefix));
|
|
28
|
+
const hasCommonSuffix = commonSuffixes.some(suffix => lowerWord.endsWith(suffix));
|
|
29
|
+
|
|
30
|
+
// Very permissive check - if it looks word-like, allow it
|
|
31
|
+
// Real implementation would check against dictionary
|
|
32
|
+
return hasCommonPrefix || hasCommonSuffix || lowerWord.length >= 3;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Create initial word chain state
|
|
36
|
+
function createInitialWordChainState() {
|
|
37
|
+
return {
|
|
38
|
+
words: [],
|
|
39
|
+
currentPlayer: 'player1', // player1 starts (the person who initiated the game)
|
|
40
|
+
moves: 0,
|
|
41
|
+
gameOver: false,
|
|
42
|
+
winner: null,
|
|
43
|
+
lastLetter: null,
|
|
44
|
+
usedWords: new Set() // Track words to prevent repeats
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Make a word chain move
|
|
49
|
+
function makeMove(gameState, word, isPlayer1Move) {
|
|
50
|
+
const { words, currentPlayer, moves, usedWords, lastLetter } = gameState;
|
|
51
|
+
|
|
52
|
+
// Normalize word
|
|
53
|
+
const normalizedWord = word.toLowerCase().trim();
|
|
54
|
+
|
|
55
|
+
// Validate basic word format
|
|
56
|
+
if (!isValidWord(normalizedWord)) {
|
|
57
|
+
return { error: 'Invalid word format. Use only letters, 2-20 characters.' };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check if it's the right player's turn
|
|
61
|
+
const expectedPlayer = currentPlayer === 'player1' ? true : false;
|
|
62
|
+
if (isPlayer1Move !== expectedPlayer) {
|
|
63
|
+
return { error: 'Not your turn!' };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check if word was already used
|
|
67
|
+
if (usedWords.has(normalizedWord)) {
|
|
68
|
+
return { error: 'Word already used! Try a different word.' };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check if word starts with the required letter
|
|
72
|
+
if (lastLetter && normalizedWord[0] !== lastLetter) {
|
|
73
|
+
return { error: `Word must start with "${lastLetter.toUpperCase()}"` };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Valid move - update game state
|
|
77
|
+
const newWords = [...words, normalizedWord];
|
|
78
|
+
const newUsedWords = new Set([...usedWords, normalizedWord]);
|
|
79
|
+
const newLastLetter = normalizedWord[normalizedWord.length - 1];
|
|
80
|
+
const newCurrentPlayer = currentPlayer === 'player1' ? 'player2' : 'player1';
|
|
81
|
+
|
|
82
|
+
const newGameState = {
|
|
83
|
+
words: newWords,
|
|
84
|
+
currentPlayer: newCurrentPlayer,
|
|
85
|
+
moves: moves + 1,
|
|
86
|
+
gameOver: false,
|
|
87
|
+
winner: null,
|
|
88
|
+
lastLetter: newLastLetter,
|
|
89
|
+
usedWords: newUsedWords,
|
|
90
|
+
lastWord: normalizedWord
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return { success: true, gameState: newGameState };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Format word chain for display
|
|
97
|
+
function formatWordChainDisplay(gameState) {
|
|
98
|
+
const { words, currentPlayer, moves, lastLetter, lastWord } = gameState;
|
|
99
|
+
|
|
100
|
+
let display = `🔗 **Word Chain** (${moves} words)\n\n`;
|
|
101
|
+
|
|
102
|
+
if (words.length === 0) {
|
|
103
|
+
display += '**Start the chain with any word!**\n\n';
|
|
104
|
+
display += 'Next: **Player 1**';
|
|
105
|
+
} else {
|
|
106
|
+
// Show the chain
|
|
107
|
+
display += '**Chain:** ' + words.map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' → ') + '\n\n';
|
|
108
|
+
|
|
109
|
+
if (lastLetter) {
|
|
110
|
+
display += `**Next word must start with: "${lastLetter.toUpperCase()}"**\n\n`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
display += `Turn: **${currentPlayer === 'player1' ? 'Player 1' : 'Player 2'}**`;
|
|
114
|
+
|
|
115
|
+
if (lastWord) {
|
|
116
|
+
display += `\nLast word: "${lastWord.charAt(0).toUpperCase() + lastWord.slice(1)}"`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return display;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check if a letter is considered "difficult" (fewer common words start with it)
|
|
124
|
+
function isDifficultLetter(letter) {
|
|
125
|
+
const difficultLetters = ['x', 'z', 'q', 'j', 'v'];
|
|
126
|
+
return difficultLetters.includes(letter.toLowerCase());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = {
|
|
130
|
+
createInitialWordChainState,
|
|
131
|
+
makeMove,
|
|
132
|
+
formatWordChainDisplay,
|
|
133
|
+
isValidWord,
|
|
134
|
+
isDifficultLetter
|
|
135
|
+
};
|
package/index.js
CHANGED
|
@@ -6,11 +6,18 @@
|
|
|
6
6
|
* Identity, presence, DM. That's it.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
// Handle "setup" command - redirect to setup.js
|
|
10
|
+
if (process.argv.includes('setup')) {
|
|
11
|
+
require('./setup.js');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
const presence = require('./presence');
|
|
10
16
|
const config = require('./config');
|
|
11
17
|
const store = require('./store');
|
|
12
18
|
const prompts = require('./prompts');
|
|
13
19
|
const NotificationEmitter = require('./notification-emitter');
|
|
20
|
+
const authStore = require('./auth-store');
|
|
14
21
|
|
|
15
22
|
// Tools that shouldn't show presence footer (would be redundant/noisy)
|
|
16
23
|
const SKIP_FOOTER_TOOLS = ['vibe_init', 'vibe_doctor', 'vibe_test', 'vibe_update', 'vibe_settings', 'vibe_notifications'];
|
|
@@ -36,9 +43,6 @@ function inferPromptFromArgs(toolName, args) {
|
|
|
36
43
|
case 'context': return 'share context';
|
|
37
44
|
case 'summarize': return 'summarize session';
|
|
38
45
|
case 'bye': return 'end session';
|
|
39
|
-
case 'remember': return `remember about ${handle}`;
|
|
40
|
-
case 'recall': return `recall ${handle}`;
|
|
41
|
-
case 'forget': return `forget ${handle}`;
|
|
42
46
|
case 'board': return args.content ? 'post to board' : 'view board';
|
|
43
47
|
case 'observe': return args.content ? 'record observation' : 'view observations';
|
|
44
48
|
case 'invite': return 'generate invite';
|
|
@@ -50,19 +54,19 @@ function inferPromptFromArgs(toolName, args) {
|
|
|
50
54
|
case 'release': return `release ${args.reservation_id || 'reservation'}`;
|
|
51
55
|
case 'reservations': return 'list reservations';
|
|
52
56
|
case 'solo_game': return `play ${args.game || 'game'}`;
|
|
53
|
-
case '
|
|
54
|
-
case 'wordassociation': return args.word ? `word association: ${args.word}` : 'play word association';
|
|
57
|
+
case 'game': return `play ${args.game || 'game'} with ${handle}`;
|
|
55
58
|
case 'multiplayer_game': return `multiplayer ${args.game || 'game'}`;
|
|
56
59
|
case 'drawing': return args.action ? `drawing ${args.action}` : 'collaborative drawing';
|
|
57
60
|
case 'crossword': return `crossword ${args.action || 'daily'}`;
|
|
58
|
-
case 'away': return args.message ? `set away: "${args.message}"` : 'go away';
|
|
59
|
-
case 'back': return 'come back';
|
|
60
61
|
case 'discover': return `discover ${args.command || 'suggest'}`;
|
|
61
62
|
case 'suggest_tags': return `suggest tags ${args.command || 'suggest'}`;
|
|
62
|
-
case '
|
|
63
|
-
case 'workshop_buddy': return `workshop buddy ${args.command || 'find'}`;
|
|
63
|
+
case 'settings': return args.mute ? `mute for ${args.mute}` : 'view settings';
|
|
64
64
|
case 'create_artifact': return `create ${args.template || 'artifact'}: ${args.title || 'untitled'}`;
|
|
65
65
|
case 'view_artifact': return args.slug ? `view artifact ${args.slug}` : `list ${args.list || 'artifacts'}`;
|
|
66
|
+
case 'broadcast': return args.action ? `broadcast ${args.action}` : 'broadcast status';
|
|
67
|
+
case 'watch': return args.target ? `watch ${args.target}` : 'list live broadcasts';
|
|
68
|
+
case 'session': return args.action ? `session ${args.action}` : 'list sessions';
|
|
69
|
+
case 'chat': return args.message ? `chat "${args.message.slice(0, 30)}..."` : 'view chat';
|
|
66
70
|
default: return `${action} ${handle}`.trim() || null;
|
|
67
71
|
}
|
|
68
72
|
}
|
|
@@ -164,45 +168,42 @@ async function getPresenceFooter() {
|
|
|
164
168
|
}
|
|
165
169
|
}
|
|
166
170
|
|
|
167
|
-
// Load
|
|
168
|
-
const
|
|
171
|
+
// Load core tools (always available)
|
|
172
|
+
const coreTools = {
|
|
169
173
|
// Entry point
|
|
170
174
|
vibe_start: require('./tools/start'),
|
|
171
175
|
// Core
|
|
172
176
|
vibe_init: require('./tools/init'),
|
|
173
|
-
vibe_token: require('./tools/token'), //
|
|
177
|
+
vibe_token: require('./tools/token'), // GitHub OAuth token setter
|
|
174
178
|
vibe_migrate: require('./tools/migrate'), // Migrate existing handle to GitHub auth
|
|
175
179
|
vibe_who: require('./tools/who'),
|
|
176
180
|
vibe_ping: require('./tools/ping'),
|
|
177
181
|
vibe_react: require('./tools/react'),
|
|
178
182
|
vibe_dm: require('./tools/dm'),
|
|
183
|
+
vibe_reply: require('./tools/reply'), // Quick reply to most recent unread
|
|
179
184
|
vibe_inbox: require('./tools/inbox'),
|
|
185
|
+
vibe_search_messages: require('./tools/search-messages'), // Search DM history
|
|
186
|
+
vibe_connection_status: require('./tools/connection-status'), // Check connection health
|
|
187
|
+
vibe_smart_check: require('./tools/smart-check'), // Smart inbox check at natural breaks
|
|
180
188
|
vibe_open: require('./tools/open'),
|
|
181
189
|
vibe_status: require('./tools/status'),
|
|
182
190
|
vibe_context: require('./tools/context'),
|
|
191
|
+
vibe_work_summary: require('./tools/work-summary'), // Ambient context for composing messages
|
|
183
192
|
vibe_summarize: require('./tools/summarize'),
|
|
184
193
|
vibe_bye: require('./tools/bye'),
|
|
185
194
|
vibe_game: require('./tools/game'),
|
|
186
195
|
vibe_solo_game: require('./tools/solo-game'),
|
|
187
196
|
vibe_party_game: require('./tools/party-game'),
|
|
188
|
-
vibe_tictactoe: require('./tools/tictactoe'),
|
|
189
|
-
vibe_wordassociation: require('./tools/wordassociation'),
|
|
190
197
|
vibe_multiplayer_game: require('./tools/multiplayer-game'),
|
|
191
198
|
vibe_drawing: require('./tools/drawing'),
|
|
192
|
-
// Nostalgia Stack
|
|
193
199
|
vibe_crossword: require('./tools/crossword'),
|
|
194
|
-
vibe_away: require('./tools/away'),
|
|
195
|
-
vibe_back: require('./tools/back'),
|
|
196
200
|
// AIRC Handoff (v1) — context portability
|
|
197
201
|
vibe_handoff: require('./tools/handoff'),
|
|
198
202
|
// File reservations (advisory locks)
|
|
199
203
|
vibe_reserve: require('./tools/reserve'),
|
|
200
204
|
vibe_release: require('./tools/release'),
|
|
201
205
|
vibe_reservations: require('./tools/reservations'),
|
|
202
|
-
// Memory tools
|
|
203
|
-
vibe_remember: require('./tools/remember'),
|
|
204
|
-
vibe_recall: require('./tools/recall'),
|
|
205
|
-
vibe_forget: require('./tools/forget'),
|
|
206
|
+
// Memory tools removed — simplified UX
|
|
206
207
|
// Consent (AIRC compliance)
|
|
207
208
|
vibe_consent: require('./tools/consent'),
|
|
208
209
|
// Trust & Safety
|
|
@@ -220,20 +221,25 @@ const tools = {
|
|
|
220
221
|
vibe_ship: require('./tools/ship'),
|
|
221
222
|
vibe_feed: require('./tools/feed'),
|
|
222
223
|
vibe_insights: require('./tools/insights'),
|
|
224
|
+
// Launch Analytics — Real-time metrics dashboard
|
|
225
|
+
vibe_analytics: require('./tools/analytics'),
|
|
226
|
+
// Activity Feed — Real-time community activity
|
|
227
|
+
vibe_activity: require('./tools/activity'),
|
|
223
228
|
// Artifacts — Just-in-time social objects
|
|
224
229
|
vibe_create_artifact: require('./tools/artifact-create'),
|
|
225
230
|
vibe_view_artifact: require('./tools/artifact-view'),
|
|
231
|
+
// Plans — Async approval workflow
|
|
232
|
+
vibe_plan: require('./tools/plan'),
|
|
233
|
+
vibe_approve: require('./tools/approve'),
|
|
226
234
|
// Discovery & Matchmaking
|
|
227
235
|
vibe_discover: require('./tools/discover'),
|
|
228
236
|
vibe_suggest_tags: require('./tools/suggest-tags'),
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
vibe_doctor: require('./tools/doctor'),
|
|
236
|
-
vibe_update: require('./tools/update'),
|
|
237
|
+
// Follow system
|
|
238
|
+
vibe_follow: require('./tools/follow'),
|
|
239
|
+
// GitHub friends import (Plaxo move)
|
|
240
|
+
vibe_friends: require('./tools/friends'),
|
|
241
|
+
// Scheduling
|
|
242
|
+
vibe_schedule: require('./tools/schedule'),
|
|
237
243
|
// @echo feedback agent (by Flynn)
|
|
238
244
|
vibe_echo: require('./tools/echo'),
|
|
239
245
|
// X/Twitter bridge
|
|
@@ -242,29 +248,62 @@ const tools = {
|
|
|
242
248
|
// Unified social inbox (Phase 1a)
|
|
243
249
|
vibe_social_inbox: require('./tools/social-inbox'),
|
|
244
250
|
vibe_social_post: require('./tools/social-post'),
|
|
245
|
-
// Language evolution
|
|
246
|
-
vibe_patterns: require('./tools/patterns'),
|
|
247
251
|
// Settings
|
|
248
252
|
vibe_settings: require('./tools/settings'),
|
|
249
253
|
// External notification channels
|
|
250
254
|
vibe_notifications: require('./tools/notifications'),
|
|
251
255
|
// Background presence agent (Claude Code 2.1)
|
|
252
256
|
vibe_presence_agent: require('./tools/presence-agent'),
|
|
253
|
-
vibe_mute: require('./tools/mute'),
|
|
254
257
|
// Onboarding checklist
|
|
255
258
|
vibe_onboarding: require('./tools/onboarding'),
|
|
256
259
|
// VIBE L2 tools
|
|
257
260
|
vibe_l2: require('./tools/l2'),
|
|
258
261
|
vibe_l2_status: require('./tools/l2-status'),
|
|
259
262
|
vibe_shipback: require('./tools/shipback'),
|
|
260
|
-
vibe_bridge: require('./tools/l2-bridge')
|
|
263
|
+
vibe_bridge: require('./tools/l2-bridge'),
|
|
264
|
+
// Gig Marketplace & Proof-of-Work
|
|
265
|
+
vibe_gig_browse: require('./tools/gig-browse'),
|
|
266
|
+
vibe_gig_complete: require('./tools/gig-complete'),
|
|
267
|
+
vibe_proof_of_work: require('./tools/proof-of-work'),
|
|
268
|
+
vibe_artifacts_price: require('./tools/artifacts-price'),
|
|
269
|
+
// Watch Me Code — Live broadcasting & sessions
|
|
270
|
+
vibe_broadcast: require('./tools/broadcast'),
|
|
271
|
+
vibe_watch: require('./tools/watch'),
|
|
272
|
+
vibe_session: require('./tools/session'),
|
|
273
|
+
vibe_chat: require('./tools/chat'),
|
|
274
|
+
// Help Signal — Symbiosis layer
|
|
275
|
+
vibe_stuck: require('./tools/stuck'),
|
|
276
|
+
// Availability — Signal openness to chat
|
|
277
|
+
vibe_available: require('./tools/available'),
|
|
278
|
+
// VIBE Economy — Tips, Earnings, Withdrawals, Subscriptions
|
|
279
|
+
vibe_tip: require('./tools/tip'),
|
|
280
|
+
vibe_earnings: require('./tools/earnings'),
|
|
281
|
+
vibe_withdraw: require('./tools/withdraw'),
|
|
282
|
+
vibe_session_price: require('./tools/session_price'),
|
|
283
|
+
vibe_subscribe: require('./tools/subscribe'),
|
|
284
|
+
vibe_subscriptions: require('./tools/subscriptions')
|
|
261
285
|
};
|
|
262
286
|
|
|
287
|
+
// Admin tools (only loaded when VIBE_ADMIN=true)
|
|
288
|
+
const adminTools = process.env.VIBE_ADMIN === 'true' ? {
|
|
289
|
+
vibe_admin_inbox: require('./tools/admin-inbox'),
|
|
290
|
+
vibe_test: require('./tools/test'),
|
|
291
|
+
vibe_doctor: require('./tools/doctor'),
|
|
292
|
+
vibe_update: require('./tools/update'),
|
|
293
|
+
vibe_patterns: require('./tools/patterns'),
|
|
294
|
+
} : {};
|
|
295
|
+
|
|
296
|
+
// Combine tools
|
|
297
|
+
const tools = { ...coreTools, ...adminTools };
|
|
298
|
+
|
|
263
299
|
/**
|
|
264
300
|
* MCP Protocol Handler
|
|
265
301
|
*/
|
|
266
302
|
class VibeMCPServer {
|
|
267
303
|
constructor() {
|
|
304
|
+
// Hydrate auth state from disk FIRST (before any tools need it)
|
|
305
|
+
authStore.hydrate();
|
|
306
|
+
|
|
268
307
|
// Initialize notification emitter
|
|
269
308
|
this.notifier = new NotificationEmitter(this);
|
|
270
309
|
|
|
@@ -345,9 +384,10 @@ class VibeMCPServer {
|
|
|
345
384
|
// Emit list_changed notification for state-changing tools
|
|
346
385
|
// This triggers Claude to refresh without reconnection
|
|
347
386
|
const stateChangingTools = [
|
|
348
|
-
'vibe_dm', 'vibe_ping', 'vibe_react',
|
|
387
|
+
'vibe_dm', 'vibe_ping', 'vibe_react',
|
|
349
388
|
'vibe_status', 'vibe_context', 'vibe_handoff',
|
|
350
|
-
'vibe_reserve', 'vibe_release'
|
|
389
|
+
'vibe_reserve', 'vibe_release',
|
|
390
|
+
'vibe_broadcast', 'vibe_session', 'vibe_chat'
|
|
351
391
|
];
|
|
352
392
|
if (stateChangingTools.includes(params.name)) {
|
|
353
393
|
// Debounced notification (prevents spam)
|
|
@@ -360,29 +400,13 @@ class VibeMCPServer {
|
|
|
360
400
|
footer = await getPresenceFooter();
|
|
361
401
|
}
|
|
362
402
|
|
|
363
|
-
// Build simplified hint indicator for Claude (human-readable)
|
|
364
|
-
let hintIndicator = '';
|
|
365
|
-
if (result.hint) {
|
|
366
|
-
// Simple format: <!-- vibe: hint_type @handle (count) -->
|
|
367
|
-
const hint = result.hint;
|
|
368
|
-
const handle = result.suggestion?.handle || result.for_handle || '';
|
|
369
|
-
const count = result.unread_count || '';
|
|
370
|
-
|
|
371
|
-
// Build minimal hint string
|
|
372
|
-
let hintParts = [hint];
|
|
373
|
-
if (handle) hintParts.push(`@${handle.replace('@', '')}`);
|
|
374
|
-
if (count) hintParts.push(`(${count})`);
|
|
375
|
-
|
|
376
|
-
hintIndicator = `\n\n<!-- vibe: ${hintParts.join(' ')} -->`;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
403
|
return {
|
|
380
404
|
jsonrpc: '2.0',
|
|
381
405
|
id,
|
|
382
406
|
result: {
|
|
383
407
|
content: [{
|
|
384
408
|
type: 'text',
|
|
385
|
-
text: (result.display || JSON.stringify(result, null, 2)) +
|
|
409
|
+
text: (result.display || JSON.stringify(result, null, 2)) + footer
|
|
386
410
|
}]
|
|
387
411
|
}
|
|
388
412
|
};
|
|
@@ -443,7 +467,7 @@ class VibeMCPServer {
|
|
|
443
467
|
|
|
444
468
|
async checkForUpdates() {
|
|
445
469
|
try {
|
|
446
|
-
const { checkForUpdates, formatUpdateNotification } =
|
|
470
|
+
const { checkForUpdates, formatUpdateNotification } = require('./auto-update');
|
|
447
471
|
const update = await checkForUpdates();
|
|
448
472
|
|
|
449
473
|
if (update) {
|
package/intelligence/index.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Intelligence Module — Ambient Social Awareness
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Five layers of intelligence:
|
|
5
5
|
* 1. Infer — Smart state detection from context signals
|
|
6
6
|
* 2. Serendipity — Surface meaningful coincidences
|
|
7
7
|
* 3. Proactive — Background agent for social moments
|
|
8
8
|
* 4. Patterns — Persistent behavioral memory (work, social, creative)
|
|
9
|
+
* 5. Interests — Live interest inference from context (Phase 2)
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
const infer = require('./infer');
|
|
12
13
|
const serendipity = require('./serendipity');
|
|
13
14
|
const proactive = require('./proactive');
|
|
14
15
|
const patterns = require('./patterns');
|
|
16
|
+
const interests = require('./interests');
|
|
15
17
|
|
|
16
18
|
module.exports = {
|
|
17
19
|
// Inference
|
|
@@ -34,5 +36,10 @@ module.exports = {
|
|
|
34
36
|
setSessionStart: proactive.setSessionStart,
|
|
35
37
|
|
|
36
38
|
// Patterns (persistent behavioral memory)
|
|
37
|
-
patterns
|
|
39
|
+
patterns,
|
|
40
|
+
|
|
41
|
+
// Interests (live context inference - Phase 2)
|
|
42
|
+
inferLiveInterests: interests.inferLiveInterests,
|
|
43
|
+
mergeInterests: interests.mergeInterests,
|
|
44
|
+
formatLiveInterests: interests.formatLiveInterests
|
|
38
45
|
};
|