slashvibe-mcp 0.3.22 → 0.3.24

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 (45) hide show
  1. package/analytics.js +107 -0
  2. package/auto-update.js +125 -0
  3. package/bridges/agent-gateway.js +351 -0
  4. package/bridges/bridge-monitor.js +399 -0
  5. package/bridges/discord-bot.js +421 -0
  6. package/bridges/farcaster.js +299 -0
  7. package/bridges/telegram.js +261 -0
  8. package/bridges/webhook-health.js +416 -0
  9. package/bridges/webhook-server.js +461 -0
  10. package/bridges/whatsapp.js +441 -0
  11. package/bridges/x-webhook.js +406 -0
  12. package/debug.js +12 -0
  13. package/eslint.config.js +54 -0
  14. package/games/arcade.js +403 -0
  15. package/games/chess.js +460 -0
  16. package/games/colorguess.js +344 -0
  17. package/games/crossword-words.js +175 -0
  18. package/games/crossword.js +463 -0
  19. package/games/drawing.js +352 -0
  20. package/games/gameroulette.js +290 -0
  21. package/games/gamerouter.js +334 -0
  22. package/games/gamestatus.js +337 -0
  23. package/games/guessnumber.js +209 -0
  24. package/games/hangman.js +330 -0
  25. package/games/memory.js +360 -0
  26. package/games/multiplayer-tictactoe.js +406 -0
  27. package/games/pixelart.js +406 -0
  28. package/games/quickduel.js +382 -0
  29. package/games/riddle.js +371 -0
  30. package/games/rockpaperscissors.js +284 -0
  31. package/games/snake.js +408 -0
  32. package/games/storybuilder.js +351 -0
  33. package/games/tictactoe.js +350 -0
  34. package/games/twentyquestions.js +379 -0
  35. package/games/twotruths.js +207 -0
  36. package/games/werewolf.js +506 -0
  37. package/games/wordassociation.js +293 -0
  38. package/games/wordchain.js +158 -0
  39. package/migrate-v2.js +72 -0
  40. package/notification-emitter.js +77 -0
  41. package/package.json +4 -10
  42. package/post-install.js +141 -0
  43. package/test-skills-bootstrap.js +20 -0
  44. package/test-v2-integration.js +385 -0
  45. package/webhook-runner.js +132 -0
package/analytics.js ADDED
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Analytics — Retention event tracking from MCP server
3
+ *
4
+ * Logs events to the /api/analytics/event endpoint for measuring
5
+ * user engagement and retention funnel performance.
6
+ *
7
+ * Usage:
8
+ * const analytics = require('./analytics');
9
+ * analytics.track('empty_inbox_action', { action: 'discover', source: 'inbox' });
10
+ */
11
+
12
+ const config = require('./config');
13
+
14
+ const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
15
+
16
+ /**
17
+ * Track an analytics event (fire and forget)
18
+ * @param {string} eventType - Event type (from valid types in api/lib/events.js)
19
+ * @param {object} data - Additional event data
20
+ */
21
+ async function track(eventType, data = {}) {
22
+ const handle = config.getHandle();
23
+ if (!handle) return; // Skip if not initialized
24
+
25
+ try {
26
+ // Fire and forget - don't await or block
27
+ fetch(`${API_URL}/api/analytics/events`, {
28
+ method: 'POST',
29
+ headers: { 'Content-Type': 'application/json' },
30
+ body: JSON.stringify({
31
+ type: eventType,
32
+ handle,
33
+ data: {
34
+ ...data,
35
+ client: 'mcp-server',
36
+ timestamp: Date.now()
37
+ }
38
+ })
39
+ }).catch(() => {}); // Silently ignore errors
40
+ } catch (e) {
41
+ // Analytics should never block or fail user flows
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Track empty inbox interaction
47
+ * @param {string} action - Which action was taken (or 'none' if user closed)
48
+ * @param {object} context - Context about the state (hadOnboardingTask, hadRecentShips, etc.)
49
+ */
50
+ function trackEmptyInbox(action, context = {}) {
51
+ // Track that user reached empty inbox state
52
+ track('empty_inbox_reached', {
53
+ hadRecentThreads: context.recentThreads?.length > 0,
54
+ hadOnboardingTask: !!context.onboardingTask,
55
+ hadRecentShips: context.recentShips?.length > 0
56
+ });
57
+
58
+ // If an action was taken, track it
59
+ if (action && action !== 'none') {
60
+ track('empty_inbox_action', {
61
+ action,
62
+ ...context
63
+ });
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Track lurk mode state change
69
+ * @param {boolean} enabled - Whether lurk mode was enabled or disabled
70
+ */
71
+ function trackLurkMode(enabled) {
72
+ track(enabled ? 'lurk_mode_enabled' : 'lurk_mode_disabled', {});
73
+ }
74
+
75
+ /**
76
+ * Track onboarding task completion
77
+ * @param {string} taskId - The task that was completed
78
+ */
79
+ function trackOnboardingTask(taskId) {
80
+ track('onboarding_task_completed', { taskId });
81
+ }
82
+
83
+ /**
84
+ * Track discovery initiation
85
+ * @param {string} source - Where discovery was initiated from (inbox, start, etc.)
86
+ */
87
+ function trackDiscovery(source) {
88
+ track('discovery_initiated', { source });
89
+ }
90
+
91
+ /**
92
+ * Track session lifecycle
93
+ * @param {string} event - 'started' or 'ended'
94
+ * @param {object} sessionData - Session metrics (duration, actions, etc.)
95
+ */
96
+ function trackSession(event, sessionData = {}) {
97
+ track(event === 'started' ? 'session_started' : 'session_ended', sessionData);
98
+ }
99
+
100
+ module.exports = {
101
+ track,
102
+ trackEmptyInbox,
103
+ trackLurkMode,
104
+ trackOnboardingTask,
105
+ trackDiscovery,
106
+ trackSession
107
+ };
package/auto-update.js ADDED
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Auto-update mechanism for /vibe MCP server
3
+ * Checks for updates and prompts user to update
4
+ */
5
+
6
+ import { exec } from 'child_process';
7
+ import { promisify } from 'util';
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const execAsync = promisify(exec);
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+
15
+ export async function checkForUpdates() {
16
+ try {
17
+ // Read local version
18
+ const versionPath = path.join(__dirname, 'version.json');
19
+ const localVersion = JSON.parse(await fs.readFile(versionPath, 'utf-8'));
20
+
21
+ // Check remote version
22
+ const response = await fetch('https://www.slashvibe.dev/api/version', {
23
+ headers: { 'User-Agent': 'vibe-mcp-client' }
24
+ });
25
+
26
+ if (!response.ok) {
27
+ return null; // Silent fail - don't block startup
28
+ }
29
+
30
+ const remoteVersion = await response.json();
31
+
32
+ // Compare versions
33
+ if (compareVersions(remoteVersion.version, localVersion.version) > 0) {
34
+ return {
35
+ current: localVersion.version,
36
+ latest: remoteVersion.version,
37
+ changelog: remoteVersion.changelog,
38
+ features: remoteVersion.features,
39
+ breaking: remoteVersion.breaking
40
+ };
41
+ }
42
+
43
+ return null; // Up to date
44
+ } catch (error) {
45
+ // Silent fail - don't block startup
46
+ return null;
47
+ }
48
+ }
49
+
50
+ export async function performUpdate() {
51
+ try {
52
+ const repoPath = path.join(__dirname, '..');
53
+
54
+ // Check if we're in a git repo
55
+ try {
56
+ await execAsync('git rev-parse --git-dir', { cwd: repoPath });
57
+ } catch {
58
+ throw new Error('Not a git repository. Manual update required.');
59
+ }
60
+
61
+ // Stash any local changes
62
+ await execAsync('git stash', { cwd: repoPath });
63
+
64
+ // Pull latest
65
+ const { stdout, stderr } = await execAsync('git pull origin main', { cwd: repoPath });
66
+
67
+ // Pop stash if we had changes
68
+ try {
69
+ await execAsync('git stash pop', { cwd: repoPath });
70
+ } catch {
71
+ // No stash to pop, that's fine
72
+ }
73
+
74
+ return {
75
+ success: true,
76
+ output: stdout,
77
+ message: 'Update successful! Restart /vibe to apply changes.'
78
+ };
79
+ } catch (error) {
80
+ return {
81
+ success: false,
82
+ error: error.message
83
+ };
84
+ }
85
+ }
86
+
87
+ export function compareVersions(v1, v2) {
88
+ const parts1 = v1.split('.').map(Number);
89
+ const parts2 = v2.split('.').map(Number);
90
+
91
+ for (let i = 0; i < 3; i++) {
92
+ if (parts1[i] > parts2[i]) return 1;
93
+ if (parts1[i] < parts2[i]) return -1;
94
+ }
95
+
96
+ return 0;
97
+ }
98
+
99
+ export function formatUpdateNotification(update) {
100
+ if (!update) return null;
101
+
102
+ let message = `\n${'='.repeat(60)}\n`;
103
+ message += `📦 /vibe UPDATE AVAILABLE\n`;
104
+ message += `${'='.repeat(60)}\n\n`;
105
+ message += `Current: v${update.current}\n`;
106
+ message += `Latest: v${update.latest}${update.breaking ? ' ⚠️ BREAKING' : ''}\n\n`;
107
+ message += `${update.changelog}\n\n`;
108
+
109
+ if (update.features && update.features.length > 0) {
110
+ message += `New features:\n`;
111
+ update.features.forEach(f => {
112
+ message += ` • ${f}\n`;
113
+ });
114
+ message += `\n`;
115
+ }
116
+
117
+ message += `Update now:\n`;
118
+ message += ` vibe update\n`;
119
+ message += `\n`;
120
+ message += `Or manually:\n`;
121
+ message += ` cd ~/.vibe/vibe-repo && git pull origin main\n`;
122
+ message += `${'='.repeat(60)}\n`;
123
+
124
+ return message;
125
+ }
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Agent Gateway Bridge — Event push + local state for external agents
3
+ *
4
+ * The /vibe platform API (slashvibe.dev) already provides:
5
+ * - Messaging: POST/GET /api/messages
6
+ * - Presence: POST/GET /api/presence
7
+ * - Board: POST/GET /api/board (ships, ideas, requests)
8
+ * - Discovery: GET /api/discover
9
+ * - Agents: GET /api/agents
10
+ * - Auth: JWT via /api/auth/*
11
+ *
12
+ * This bridge fills the GAPS for external agent gateways (Clawdbot, @seth):
13
+ *
14
+ * 1. EVENT PUSH — Platform is pull-based. This pushes events to agents.
15
+ * 2. LOCAL STATE — Memory, reservations, and session data are local-only.
16
+ * 3. AIRC IDENTITY — Verifies agent identity via Ed25519 signatures.
17
+ * 4. AGENT REGISTRY — Tracks which agents are connected + their capabilities.
18
+ *
19
+ * External agents should use the platform API directly for:
20
+ * DMs, presence, board, discovery, profiles
21
+ * And use THIS bridge for:
22
+ * Event subscriptions, local memory queries, AIRC verification
23
+ */
24
+
25
+ const crypto = require('../crypto');
26
+ const config = require('../config');
27
+ const memory = require('../memory');
28
+ const debug = require('../debug');
29
+
30
+ // ============ AGENT REGISTRY ============
31
+
32
+ /**
33
+ * Known agent gateways
34
+ * @type {Map<string, {handle: string, publicKey: string, endpoint: string, capabilities: string[], registeredAt: number}>}
35
+ */
36
+ const agentRegistry = new Map();
37
+
38
+ /**
39
+ * Event subscriptions — agents subscribe to event types and get HTTP pushes
40
+ * @type {Map<string, {endpoint: string, events: string[], handle: string}>}
41
+ */
42
+ const eventSubscriptions = new Map();
43
+
44
+ /**
45
+ * Register an external agent gateway with AIRC identity
46
+ *
47
+ * @param {object} params
48
+ * @param {string} params.handle Agent handle (e.g. "seth-agent")
49
+ * @param {string} params.publicKey Base64 Ed25519 public key (AIRC)
50
+ * @param {string} [params.endpoint] HTTP callback URL for event pushes
51
+ * @param {string[]} [params.capabilities] What this agent can do
52
+ * @param {string} [params.signature] AIRC signature proving key ownership
53
+ * @returns {{success: boolean, agentId?: string, error?: string}}
54
+ */
55
+ function registerAgent({ handle, publicKey, endpoint, capabilities = [], signature }) {
56
+ if (!handle || !publicKey) {
57
+ return { success: false, error: 'handle and publicKey required' };
58
+ }
59
+
60
+ // Verify AIRC signature if provided (proves private key ownership)
61
+ if (signature) {
62
+ const valid = crypto.verify(
63
+ { handle, publicKey, endpoint, capabilities },
64
+ publicKey
65
+ );
66
+ if (!valid) {
67
+ return { success: false, error: 'Invalid AIRC signature' };
68
+ }
69
+ }
70
+
71
+ const agentId = `agent_${handle}_${Date.now().toString(36)}`;
72
+
73
+ agentRegistry.set(handle, {
74
+ agentId,
75
+ handle,
76
+ publicKey,
77
+ endpoint: endpoint || null,
78
+ capabilities,
79
+ registeredAt: Date.now()
80
+ });
81
+
82
+ debug(`[agent-gateway] Registered @${handle} (${agentId})`);
83
+ return { success: true, agentId, handle };
84
+ }
85
+
86
+ /**
87
+ * Verify an AIRC-signed message from a registered agent
88
+ *
89
+ * @param {object} message Signed message with `from` and `signature` fields
90
+ * @returns {{valid: boolean, handle?: string, verified?: string, error?: string}}
91
+ */
92
+ function verifyAgentMessage(message) {
93
+ if (!message || !message.from) {
94
+ return { valid: false, error: 'Missing from field' };
95
+ }
96
+
97
+ const agent = agentRegistry.get(message.from);
98
+
99
+ // AIRC-verified: registered agent with valid signature
100
+ if (agent && message.signature) {
101
+ const valid = crypto.verify(message, agent.publicKey);
102
+ if (!valid) {
103
+ return { valid: false, error: 'AIRC signature verification failed' };
104
+ }
105
+ return { valid: true, handle: message.from, verified: 'airc' };
106
+ }
107
+
108
+ // Registered but unsigned — allow with lower trust
109
+ if (agent && !message.signature) {
110
+ debug(`[agent-gateway] Unsigned request from registered agent @${message.from}`);
111
+ return { valid: true, handle: message.from, verified: 'registered' };
112
+ }
113
+
114
+ return { valid: false, error: `Unknown agent: ${message.from}. Register first via POST /agent/register` };
115
+ }
116
+
117
+ // ============ EVENT PUSH ============
118
+
119
+ /**
120
+ * Subscribe an agent to /vibe events (push model)
121
+ *
122
+ * Event types:
123
+ * - dm: New direct messages for the subscribed handle
124
+ * - mention: @mentions in feed/board
125
+ * - ship: New ships from connections
126
+ * - presence: People coming online/offline
127
+ * - handoff: Task handoff requests
128
+ *
129
+ * @param {string} handle Agent handle
130
+ * @param {string} endpoint HTTP callback URL to receive events
131
+ * @param {string[]} events Event types to subscribe to
132
+ * @returns {{success: boolean, subscribed: string[]}}
133
+ */
134
+ function subscribe(handle, endpoint, events = ['dm', 'mention', 'ship', 'presence']) {
135
+ if (!endpoint) {
136
+ return { success: false, error: 'endpoint required' };
137
+ }
138
+
139
+ eventSubscriptions.set(handle, { endpoint, events, handle });
140
+ debug(`[agent-gateway] @${handle} subscribed to [${events.join(', ')}] → ${endpoint}`);
141
+ return { success: true, subscribed: events };
142
+ }
143
+
144
+ /**
145
+ * Unsubscribe an agent from events
146
+ * @param {string} handle Agent handle
147
+ */
148
+ function unsubscribe(handle) {
149
+ eventSubscriptions.delete(handle);
150
+ debug(`[agent-gateway] @${handle} unsubscribed`);
151
+ return { success: true };
152
+ }
153
+
154
+ /**
155
+ * Push an event to all subscribed agents
156
+ * AIRC-signed if we have a keypair (proves event came from /vibe)
157
+ *
158
+ * Called by notify.js and tool handlers when events occur.
159
+ *
160
+ * @param {string} eventType Event type (dm, mention, ship, presence, handoff)
161
+ * @param {object} eventData Event payload
162
+ */
163
+ async function pushEvent(eventType, eventData) {
164
+ const keypair = config.getKeypair();
165
+ const myHandle = config.getHandle();
166
+
167
+ for (const [handle, sub] of eventSubscriptions) {
168
+ if (!sub.events.includes(eventType)) continue;
169
+ if (!sub.endpoint) continue;
170
+
171
+ const event = {
172
+ v: '0.1',
173
+ type: 'vibe_event',
174
+ event: eventType,
175
+ data: eventData,
176
+ from: myHandle || 'vibe-mcp',
177
+ timestamp: Math.floor(Date.now() / 1000)
178
+ };
179
+
180
+ // AIRC sign so receiver can verify this came from /vibe
181
+ if (keypair) {
182
+ event.signature = crypto.sign(event, keypair.privateKey);
183
+ event.publicKey = keypair.publicKey;
184
+ }
185
+
186
+ try {
187
+ const response = await fetch(sub.endpoint, {
188
+ method: 'POST',
189
+ headers: {
190
+ 'Content-Type': 'application/json',
191
+ 'X-Vibe-Event': eventType,
192
+ 'X-Vibe-Source': 'vibe-mcp'
193
+ },
194
+ body: JSON.stringify(event)
195
+ });
196
+
197
+ if (!response.ok) {
198
+ debug(`[agent-gateway] Push to @${handle} failed: HTTP ${response.status}`);
199
+ }
200
+ } catch (e) {
201
+ debug(`[agent-gateway] Push to @${handle} failed: ${e.message}`);
202
+ }
203
+ }
204
+ }
205
+
206
+ // ============ LOCAL STATE QUERIES ============
207
+ // These expose data that lives only in the MCP process, not on the platform API
208
+
209
+ /**
210
+ * Query local memory (thread-scoped JSONL files)
211
+ * Platform API doesn't have memory — it's local-first by design
212
+ */
213
+ function queryMemory(handle, limit = 10, search = null) {
214
+ const memories = memory.recall(handle, limit);
215
+
216
+ if (search && memories.length > 0) {
217
+ return memories.filter(m =>
218
+ m.observation.toLowerCase().includes(search.toLowerCase())
219
+ );
220
+ }
221
+
222
+ return memories;
223
+ }
224
+
225
+ /**
226
+ * Store a memory observation (local-first)
227
+ */
228
+ function storeMemory(handle, observation) {
229
+ memory.remember(handle, observation);
230
+ return { success: true, handle, observation };
231
+ }
232
+
233
+ /**
234
+ * List all memory threads
235
+ */
236
+ function listMemoryThreads() {
237
+ return memory.listThreads();
238
+ }
239
+
240
+ // ============ HTTP HANDLER ============
241
+
242
+ /**
243
+ * HTTP handler for the agent gateway
244
+ *
245
+ * Routes:
246
+ * POST /agent/register — Register agent with AIRC public key
247
+ * POST /agent/subscribe — Subscribe to event pushes
248
+ * POST /agent/unsubscribe — Unsubscribe from events
249
+ * POST /agent/memory — Query/store local memory
250
+ * GET /agent/status — Gateway health + registered agents
251
+ *
252
+ * For everything else, agents hit the platform API directly:
253
+ * POST https://slashvibe.dev/api/messages — Send DMs
254
+ * GET https://slashvibe.dev/api/presence — Who's online
255
+ * POST https://slashvibe.dev/api/board — Ship/idea/request
256
+ * GET https://slashvibe.dev/api/discover — Find people
257
+ * GET https://slashvibe.dev/api/agents — Agent directory
258
+ */
259
+ async function handleRequest(req) {
260
+ const { path, method, body } = req;
261
+
262
+ // Health / status
263
+ if (path === '/agent/status' && method === 'GET') {
264
+ const agents = [];
265
+ for (const [, agent] of agentRegistry) {
266
+ agents.push({
267
+ handle: agent.handle,
268
+ capabilities: agent.capabilities,
269
+ registeredAt: agent.registeredAt,
270
+ hasEndpoint: !!agent.endpoint
271
+ });
272
+ }
273
+
274
+ return {
275
+ status: 'ok',
276
+ agents,
277
+ subscriptions: eventSubscriptions.size,
278
+ version: '0.1.0',
279
+ platform_api: config.getApiUrl(),
280
+ note: 'For DMs, presence, board, discovery — use the platform API directly'
281
+ };
282
+ }
283
+
284
+ if (method !== 'POST') {
285
+ return { error: 'Method not allowed', status: 405 };
286
+ }
287
+
288
+ const data = typeof body === 'string' ? JSON.parse(body) : body;
289
+
290
+ switch (path) {
291
+ case '/agent/register':
292
+ return registerAgent(data);
293
+
294
+ case '/agent/subscribe': {
295
+ const v = verifyAgentMessage(data);
296
+ if (!v.valid) return { success: false, error: v.error, status: 401 };
297
+ return subscribe(v.handle, data.endpoint, data.events);
298
+ }
299
+
300
+ case '/agent/unsubscribe': {
301
+ const v = verifyAgentMessage(data);
302
+ if (!v.valid) return { success: false, error: v.error, status: 401 };
303
+ return unsubscribe(v.handle);
304
+ }
305
+
306
+ case '/agent/memory': {
307
+ const v = verifyAgentMessage(data);
308
+ if (!v.valid) return { success: false, error: v.error, status: 401 };
309
+
310
+ if (data.action === 'recall') {
311
+ const memories = queryMemory(data.handle, data.limit, data.search);
312
+ return { success: true, memories };
313
+ }
314
+ if (data.action === 'remember') {
315
+ return storeMemory(data.handle, data.observation);
316
+ }
317
+ if (data.action === 'threads') {
318
+ return { success: true, threads: listMemoryThreads() };
319
+ }
320
+ return { success: false, error: 'action must be: recall, remember, or threads' };
321
+ }
322
+
323
+ default:
324
+ return { error: 'Not found', status: 404 };
325
+ }
326
+ }
327
+
328
+ // ============ EXPORTS ============
329
+
330
+ module.exports = {
331
+ // Registration
332
+ registerAgent,
333
+ verifyAgentMessage,
334
+
335
+ // Event push (the main value-add over platform API)
336
+ subscribe,
337
+ unsubscribe,
338
+ pushEvent,
339
+
340
+ // Local state (not on platform)
341
+ queryMemory,
342
+ storeMemory,
343
+ listMemoryThreads,
344
+
345
+ // HTTP handler
346
+ handleRequest,
347
+
348
+ // Registry access
349
+ getAgentRegistry: () => agentRegistry,
350
+ getSubscriptions: () => eventSubscriptions
351
+ };