slashvibe-mcp 0.2.9 → 0.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/README.md +1 -0
  2. package/analytics.js +107 -0
  3. package/auth-store.js +148 -0
  4. package/auto-update.js +130 -0
  5. package/bridges/bridge-monitor.js +388 -0
  6. package/bridges/discord-bot.js +431 -0
  7. package/bridges/farcaster.js +299 -0
  8. package/bridges/telegram.js +261 -0
  9. package/bridges/webhook-health.js +420 -0
  10. package/bridges/webhook-server.js +437 -0
  11. package/bridges/whatsapp.js +441 -0
  12. package/bridges/x-webhook.js +423 -0
  13. package/config.js +154 -5
  14. package/crypto.js +3 -8
  15. package/games/arcade.js +406 -0
  16. package/games/chess.js +451 -0
  17. package/games/colorguess.js +343 -0
  18. package/games/crossword-words.js +171 -0
  19. package/games/crossword.js +461 -0
  20. package/games/drawing.js +347 -0
  21. package/games/gameroulette.js +300 -0
  22. package/games/gamerouter.js +336 -0
  23. package/games/gamestatus.js +337 -0
  24. package/games/guessnumber.js +209 -0
  25. package/games/hangman.js +279 -0
  26. package/games/memory.js +338 -0
  27. package/games/multiplayer-tictactoe.js +389 -0
  28. package/games/pixelart.js +399 -0
  29. package/games/quickduel.js +354 -0
  30. package/games/riddle.js +371 -0
  31. package/games/rockpaperscissors.js +291 -0
  32. package/games/snake.js +406 -0
  33. package/games/storybuilder.js +343 -0
  34. package/games/tictactoe.js +345 -0
  35. package/games/twentyquestions.js +286 -0
  36. package/games/twotruths.js +207 -0
  37. package/games/werewolf.js +508 -0
  38. package/games/wordassociation.js +247 -0
  39. package/games/wordchain.js +135 -0
  40. package/index.js +105 -216
  41. package/intelligence/index.js +45 -0
  42. package/intelligence/infer.js +316 -0
  43. package/intelligence/interests.js +369 -0
  44. package/intelligence/patterns.js +651 -0
  45. package/intelligence/proactive.js +358 -0
  46. package/intelligence/serendipity.js +306 -0
  47. package/memory.js +166 -0
  48. package/notification-emitter.js +77 -0
  49. package/notify.js +102 -1
  50. package/package.json +21 -7
  51. package/prompts.js +1 -1
  52. package/protocol/index.js +161 -1
  53. package/setup.js +402 -0
  54. package/store/api.js +528 -82
  55. package/store/profiles.js +160 -12
  56. package/tools/_actions.js +463 -16
  57. package/tools/_deprecated/auto-suggest-connections.js +304 -0
  58. package/tools/_deprecated/bootstrap-skills.js +231 -0
  59. package/tools/_deprecated/bridge-dashboard.js +342 -0
  60. package/tools/_deprecated/bridge-health.js +400 -0
  61. package/tools/_deprecated/bridge-live.js +384 -0
  62. package/tools/_deprecated/bridges.js +383 -0
  63. package/tools/_deprecated/colorguess.js +281 -0
  64. package/tools/_deprecated/discover-insights.js +379 -0
  65. package/tools/_deprecated/discover-momentum.js +256 -0
  66. package/tools/_deprecated/discovery-analytics.js +345 -0
  67. package/tools/_deprecated/discovery-auto-suggest.js +275 -0
  68. package/tools/_deprecated/discovery-bootstrap.js +267 -0
  69. package/tools/_deprecated/discovery-daily.js +375 -0
  70. package/tools/_deprecated/discovery-dashboard.js +385 -0
  71. package/tools/_deprecated/discovery-digest.js +314 -0
  72. package/tools/_deprecated/discovery-hub.js +357 -0
  73. package/tools/_deprecated/discovery-insights.js +384 -0
  74. package/tools/_deprecated/discovery-momentum.js +281 -0
  75. package/tools/_deprecated/discovery-monitor.js +319 -0
  76. package/tools/_deprecated/discovery-proactive.js +300 -0
  77. package/tools/_deprecated/draw.js +317 -0
  78. package/tools/_deprecated/farcaster.js +307 -0
  79. package/tools/_deprecated/forget.js +119 -0
  80. package/tools/_deprecated/games-catalog.js +376 -0
  81. package/tools/_deprecated/games.js +313 -0
  82. package/tools/_deprecated/guessnumber.js +194 -0
  83. package/tools/_deprecated/hangman.js +129 -0
  84. package/tools/_deprecated/multiplayer-tictactoe.js +303 -0
  85. package/tools/_deprecated/recall.js +147 -0
  86. package/tools/_deprecated/remember.js +86 -0
  87. package/tools/_deprecated/riddle.js +240 -0
  88. package/tools/_deprecated/run-bootstrap.js +69 -0
  89. package/tools/_deprecated/skills-analytics.js +349 -0
  90. package/tools/_deprecated/skills-bootstrap.js +301 -0
  91. package/tools/_deprecated/skills-dashboard.js +268 -0
  92. package/tools/_deprecated/skills.js +380 -0
  93. package/tools/_deprecated/smart-intro.js +353 -0
  94. package/tools/_deprecated/storybuilder.js +331 -0
  95. package/tools/_deprecated/telegram-bot.js +183 -0
  96. package/tools/_deprecated/telegram-setup.js +214 -0
  97. package/tools/_deprecated/twentyquestions.js +143 -0
  98. package/tools/_discovery-enhanced.js +290 -0
  99. package/tools/_discovery.js +439 -0
  100. package/tools/_proactive-discovery.js +301 -0
  101. package/tools/_shared/index.js +64 -0
  102. package/tools/_shared.js +234 -0
  103. package/tools/_work-context.js +338 -0
  104. package/tools/_work-context.manual-test.js +199 -0
  105. package/tools/_work-context.test.js +260 -0
  106. package/tools/activity.js +220 -0
  107. package/tools/admin-inbox.js +218 -0
  108. package/tools/agent-treasury.js +288 -0
  109. package/tools/analytics.js +191 -0
  110. package/tools/approve.js +197 -0
  111. package/tools/arcade.js +173 -0
  112. package/tools/artifact-create.js +17 -11
  113. package/tools/artifact-view.js +31 -1
  114. package/tools/artifacts-price.js +47 -43
  115. package/tools/ask-expert.js +160 -0
  116. package/tools/available.js +120 -0
  117. package/tools/become-expert.js +150 -0
  118. package/tools/broadcast.js +286 -0
  119. package/tools/chat.js +202 -0
  120. package/tools/collaborative-drawing.js +286 -0
  121. package/tools/connection-status.js +178 -0
  122. package/tools/crossword.js +17 -1
  123. package/tools/discover.js +350 -34
  124. package/tools/dm.js +115 -73
  125. package/tools/drawing.js +1 -1
  126. package/tools/earnings.js +126 -0
  127. package/tools/echo.js +16 -0
  128. package/tools/feed.js +51 -7
  129. package/tools/follow.js +224 -0
  130. package/tools/friends.js +207 -0
  131. package/tools/game.js +2 -2
  132. package/tools/genesis.js +233 -0
  133. package/tools/gig-browse.js +206 -0
  134. package/tools/gig-complete.js +144 -0
  135. package/tools/handoff.js +7 -1
  136. package/tools/help.js +4 -4
  137. package/tools/idea.js +9 -2
  138. package/tools/inbox.js +334 -28
  139. package/tools/init.js +727 -36
  140. package/tools/invite.js +15 -4
  141. package/tools/l2-bridge.js +272 -0
  142. package/tools/l2-status.js +217 -0
  143. package/tools/l2.js +206 -0
  144. package/tools/migrate.js +156 -0
  145. package/tools/mint.js +377 -0
  146. package/tools/multiplayer-game.js +1 -1
  147. package/tools/notifications.js +415 -0
  148. package/tools/observe.js +200 -0
  149. package/tools/onboarding.js +147 -0
  150. package/tools/open.js +143 -12
  151. package/tools/party-game.js +2 -2
  152. package/tools/plan.js +225 -0
  153. package/tools/presence-agent.js +7 -0
  154. package/tools/proof-of-work.js +100 -104
  155. package/tools/pulse.js +218 -0
  156. package/tools/reply.js +166 -0
  157. package/tools/report.js +2 -2
  158. package/tools/reputation.js +175 -0
  159. package/tools/request.js +17 -3
  160. package/tools/schedule.js +367 -0
  161. package/tools/search-messages.js +123 -0
  162. package/tools/session.js +420 -0
  163. package/tools/session_price.js +128 -0
  164. package/tools/settings.js +126 -3
  165. package/tools/ship.js +31 -8
  166. package/tools/shipback.js +326 -0
  167. package/tools/smart-check.js +201 -0
  168. package/tools/social-processor.js +445 -0
  169. package/tools/solo-game.js +1 -1
  170. package/tools/start.js +335 -93
  171. package/tools/status.js +53 -6
  172. package/tools/stuck.js +297 -0
  173. package/tools/subscribe.js +148 -0
  174. package/tools/subscriptions.js +134 -0
  175. package/tools/suggest-tags.js +6 -8
  176. package/tools/tag-suggestions.js +257 -0
  177. package/tools/tip.js +193 -0
  178. package/tools/token.js +103 -0
  179. package/tools/update.js +1 -1
  180. package/tools/wallet.js +239 -186
  181. package/tools/watch.js +157 -0
  182. package/tools/webhook-test.js +388 -0
  183. package/tools/who.js +54 -3
  184. package/tools/withdraw.js +145 -0
  185. package/tools/work-summary.js +96 -0
  186. package/tools/workshop.js +327 -0
  187. package/tools/x-mentions.js +1 -1
  188. package/version.json +14 -3
  189. package/tools/artifacts-buy.js +0 -111
  190. package/tools/connect.js +0 -284
  191. package/tools/gigs-apply.js +0 -99
  192. package/tools/gigs-browse.js +0 -114
  193. package/tools/gigs-complete.js +0 -128
  194. package/tools/gigs-post.js +0 -140
  195. package/tools/live-off.js +0 -109
  196. package/tools/live-watch.js +0 -176
  197. package/tools/live.js +0 -128
  198. package/tools/sessions-browse.js +0 -105
  199. package/tools/whats-happening.js +0 -125
  200. /package/tools/{away.js → _deprecated/away.js} +0 -0
  201. /package/tools/{back.js → _deprecated/back.js} +0 -0
  202. /package/tools/{mute.js → _deprecated/mute.js} +0 -0
  203. /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
  204. /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
  205. /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
@@ -0,0 +1,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 };