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
package/memory.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /vibe Memory — Local, thread-scoped, append-only
|
|
3
|
+
*
|
|
4
|
+
* "Messages may contain meaning. Memory requires consent."
|
|
5
|
+
*
|
|
6
|
+
* Rules:
|
|
7
|
+
* - Local-first: User can inspect ~/.vibe/memory/
|
|
8
|
+
* - Explicit consent: No ambient surveillance
|
|
9
|
+
* - Thread-scoped: Memories don't leak across contexts
|
|
10
|
+
* - Inspectable: Plain JSONL, no encryption
|
|
11
|
+
* - Append-only: Audit trail, no silent edits
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const config = require('./config');
|
|
17
|
+
|
|
18
|
+
const MEMORY_DIR = path.join(config.VIBE_DIR, 'memory');
|
|
19
|
+
|
|
20
|
+
function ensureMemoryDir() {
|
|
21
|
+
if (!fs.existsSync(MEMORY_DIR)) {
|
|
22
|
+
fs.mkdirSync(MEMORY_DIR, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get the thread file path for a handle
|
|
28
|
+
* Thread files are named: thread_HANDLE.jsonl
|
|
29
|
+
*/
|
|
30
|
+
function getThreadFile(handle) {
|
|
31
|
+
ensureMemoryDir();
|
|
32
|
+
// Sanitize handle for filename
|
|
33
|
+
const safeHandle = handle.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase();
|
|
34
|
+
return path.join(MEMORY_DIR, `thread_${safeHandle}.jsonl`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Append a memory to a thread
|
|
39
|
+
* Returns the memory object with timestamp
|
|
40
|
+
*/
|
|
41
|
+
function remember(handle, observation, metadata = {}) {
|
|
42
|
+
const threadFile = getThreadFile(handle);
|
|
43
|
+
const myHandle = config.getHandle();
|
|
44
|
+
|
|
45
|
+
const memory = {
|
|
46
|
+
id: Date.now().toString(36) + Math.random().toString(36).substring(2, 8),
|
|
47
|
+
timestamp: new Date().toISOString(),
|
|
48
|
+
observation,
|
|
49
|
+
from: myHandle,
|
|
50
|
+
about: handle,
|
|
51
|
+
...metadata
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Append to JSONL file (one JSON object per line)
|
|
55
|
+
const line = JSON.stringify(memory) + '\n';
|
|
56
|
+
fs.appendFileSync(threadFile, line);
|
|
57
|
+
|
|
58
|
+
return memory;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Recall memories from a thread
|
|
63
|
+
* Returns array of memory objects, newest first
|
|
64
|
+
*/
|
|
65
|
+
function recall(handle, limit = 10) {
|
|
66
|
+
const threadFile = getThreadFile(handle);
|
|
67
|
+
|
|
68
|
+
if (!fs.existsSync(threadFile)) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const content = fs.readFileSync(threadFile, 'utf8');
|
|
74
|
+
const lines = content.trim().split('\n').filter(l => l.trim());
|
|
75
|
+
const memories = lines.map(line => {
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(line);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}).filter(m => m !== null);
|
|
82
|
+
|
|
83
|
+
// Return newest first, limited
|
|
84
|
+
return memories.reverse().slice(0, limit);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Count memories in a thread
|
|
92
|
+
*/
|
|
93
|
+
function count(handle) {
|
|
94
|
+
const memories = recall(handle, 10000);
|
|
95
|
+
return memories.length;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Forget all memories for a thread
|
|
100
|
+
* Returns number of memories deleted
|
|
101
|
+
*/
|
|
102
|
+
function forget(handle) {
|
|
103
|
+
const threadFile = getThreadFile(handle);
|
|
104
|
+
|
|
105
|
+
if (!fs.existsSync(threadFile)) {
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const countBefore = count(handle);
|
|
110
|
+
fs.unlinkSync(threadFile);
|
|
111
|
+
return countBefore;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* List all threads with memories
|
|
116
|
+
* Returns array of { handle, count, lastUpdated }
|
|
117
|
+
*/
|
|
118
|
+
function listThreads() {
|
|
119
|
+
ensureMemoryDir();
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const files = fs.readdirSync(MEMORY_DIR);
|
|
123
|
+
const threads = [];
|
|
124
|
+
|
|
125
|
+
for (const file of files) {
|
|
126
|
+
if (!file.startsWith('thread_') || !file.endsWith('.jsonl')) continue;
|
|
127
|
+
|
|
128
|
+
const handle = file.replace('thread_', '').replace('.jsonl', '');
|
|
129
|
+
const filePath = path.join(MEMORY_DIR, file);
|
|
130
|
+
const stats = fs.statSync(filePath);
|
|
131
|
+
const memories = recall(handle, 10000);
|
|
132
|
+
|
|
133
|
+
threads.push({
|
|
134
|
+
handle,
|
|
135
|
+
count: memories.length,
|
|
136
|
+
lastUpdated: stats.mtime,
|
|
137
|
+
oldestMemory: memories.length > 0 ? memories[memories.length - 1].timestamp : null,
|
|
138
|
+
newestMemory: memories.length > 0 ? memories[0].timestamp : null
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Sort by last updated, newest first
|
|
143
|
+
threads.sort((a, b) => b.lastUpdated - a.lastUpdated);
|
|
144
|
+
return threads;
|
|
145
|
+
} catch (e) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get path to memory directory (for inspection)
|
|
152
|
+
*/
|
|
153
|
+
function getMemoryPath() {
|
|
154
|
+
ensureMemoryDir();
|
|
155
|
+
return MEMORY_DIR;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
remember,
|
|
160
|
+
recall,
|
|
161
|
+
count,
|
|
162
|
+
forget,
|
|
163
|
+
listThreads,
|
|
164
|
+
getMemoryPath,
|
|
165
|
+
getThreadFile
|
|
166
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP `list_changed` notification emitter
|
|
3
|
+
*
|
|
4
|
+
* Triggers Claude to refresh tool results without reconnection.
|
|
5
|
+
* Implements debouncing to prevent notification spam.
|
|
6
|
+
*
|
|
7
|
+
* This eliminates the need for 30-second polling loops,
|
|
8
|
+
* reducing API calls by ~90% and providing instant updates.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
class NotificationEmitter {
|
|
12
|
+
constructor(server) {
|
|
13
|
+
this.server = server;
|
|
14
|
+
this.debounceTimers = {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Emit list_changed notification with debouncing
|
|
19
|
+
* @param {string} reason - Why notification is being sent (for logging/debugging)
|
|
20
|
+
* @param {number} debounceMs - Debounce window in milliseconds (default: 1000ms)
|
|
21
|
+
*/
|
|
22
|
+
emitChange(reason, debounceMs = 1000) {
|
|
23
|
+
// Debounce to prevent notification spam
|
|
24
|
+
// If we get multiple changes of the same type within the window,
|
|
25
|
+
// only emit one notification
|
|
26
|
+
if (this.debounceTimers[reason]) {
|
|
27
|
+
clearTimeout(this.debounceTimers[reason]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.debounceTimers[reason] = setTimeout(() => {
|
|
31
|
+
try {
|
|
32
|
+
this.server.notification({
|
|
33
|
+
method: "notifications/list_changed"
|
|
34
|
+
});
|
|
35
|
+
delete this.debounceTimers[reason];
|
|
36
|
+
} catch (e) {
|
|
37
|
+
// Silent fail - notifications are best-effort
|
|
38
|
+
// If notification fails, Claude will continue working normally
|
|
39
|
+
}
|
|
40
|
+
}, debounceMs);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Emit immediately without debouncing
|
|
45
|
+
* Use for urgent updates like direct mentions
|
|
46
|
+
*/
|
|
47
|
+
emitImmediate() {
|
|
48
|
+
try {
|
|
49
|
+
this.server.notification({
|
|
50
|
+
method: "notifications/list_changed"
|
|
51
|
+
});
|
|
52
|
+
} catch (e) {
|
|
53
|
+
// Silent fail
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Cancel pending notifications for a specific reason
|
|
59
|
+
* Useful when shutting down or cleaning up
|
|
60
|
+
*/
|
|
61
|
+
cancel(reason) {
|
|
62
|
+
if (this.debounceTimers[reason]) {
|
|
63
|
+
clearTimeout(this.debounceTimers[reason]);
|
|
64
|
+
delete this.debounceTimers[reason];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Cancel all pending notifications
|
|
70
|
+
*/
|
|
71
|
+
cancelAll() {
|
|
72
|
+
Object.values(this.debounceTimers).forEach(timer => clearTimeout(timer));
|
|
73
|
+
this.debounceTimers = {};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = NotificationEmitter;
|
package/notify.js
CHANGED
|
@@ -89,6 +89,8 @@ async function checkAndNotify(inbox) {
|
|
|
89
89
|
|
|
90
90
|
let notified = false;
|
|
91
91
|
|
|
92
|
+
const myHandle = config.getHandle();
|
|
93
|
+
|
|
92
94
|
for (const msg of inbox) {
|
|
93
95
|
// Skip if already notified
|
|
94
96
|
if (state.notifiedIds.includes(msg.id)) continue;
|
|
@@ -96,6 +98,9 @@ async function checkAndNotify(inbox) {
|
|
|
96
98
|
// Skip if already read
|
|
97
99
|
if (msg.read) continue;
|
|
98
100
|
|
|
101
|
+
// Skip messages FROM yourself (don't notify about your own sends)
|
|
102
|
+
if (myHandle && msg.from?.toLowerCase() === myHandle.toLowerCase()) continue;
|
|
103
|
+
|
|
99
104
|
const msgTime = new Date(msg.createdAt).getTime();
|
|
100
105
|
const age = now - msgTime;
|
|
101
106
|
|
|
@@ -104,7 +109,6 @@ async function checkAndNotify(inbox) {
|
|
|
104
109
|
let reason = '';
|
|
105
110
|
|
|
106
111
|
// Rule 1: Direct mention in message (always if not "off")
|
|
107
|
-
const myHandle = config.getHandle();
|
|
108
112
|
if (myHandle && msg.text && msg.text.toLowerCase().includes(`@${myHandle}`)) {
|
|
109
113
|
shouldNotify = true;
|
|
110
114
|
reason = 'mention';
|
|
@@ -236,6 +240,89 @@ function checkPresence(activeUsers) {
|
|
|
236
240
|
return justJoined;
|
|
237
241
|
}
|
|
238
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Notify about new ships from connections (FOMO/retention driver)
|
|
245
|
+
*/
|
|
246
|
+
const SHIPS_STATE_FILE = path.join(config.VIBE_DIR, '.ships_state.json');
|
|
247
|
+
|
|
248
|
+
function loadShipsState() {
|
|
249
|
+
try {
|
|
250
|
+
if (fs.existsSync(SHIPS_STATE_FILE)) {
|
|
251
|
+
return JSON.parse(fs.readFileSync(SHIPS_STATE_FILE, 'utf8'));
|
|
252
|
+
}
|
|
253
|
+
} catch (e) {}
|
|
254
|
+
return { seenShipIds: [], lastCheck: null };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function saveShipsState(state) {
|
|
258
|
+
try {
|
|
259
|
+
fs.writeFileSync(SHIPS_STATE_FILE, JSON.stringify(state, null, 2));
|
|
260
|
+
} catch (e) {}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Check for new ships and notify
|
|
265
|
+
* Only notifies about ships from people in memory (connections)
|
|
266
|
+
*/
|
|
267
|
+
async function checkShips(memoryHandles = []) {
|
|
268
|
+
const notifyLevel = config.getNotifications();
|
|
269
|
+
if (notifyLevel === 'off') return [];
|
|
270
|
+
|
|
271
|
+
const state = loadShipsState();
|
|
272
|
+
const now = Date.now();
|
|
273
|
+
const myHandle = config.getHandle();
|
|
274
|
+
const newShips = [];
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
// Fetch recent ships from board API
|
|
278
|
+
const apiUrl = config.getApiUrl();
|
|
279
|
+
const response = await fetch(`${apiUrl}/api/board?limit=10&category=shipped`);
|
|
280
|
+
const data = await response.json();
|
|
281
|
+
const ships = data.entries || [];
|
|
282
|
+
|
|
283
|
+
for (const ship of ships) {
|
|
284
|
+
// Skip if already seen
|
|
285
|
+
const shipId = ship.id || `${ship.author}-${ship.timestamp}`;
|
|
286
|
+
if (state.seenShipIds.includes(shipId)) continue;
|
|
287
|
+
|
|
288
|
+
// Skip our own ships
|
|
289
|
+
if (ship.author === myHandle) {
|
|
290
|
+
state.seenShipIds.push(shipId);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Only notify about ships from connections (people in memory)
|
|
295
|
+
const isConnection = memoryHandles.includes(ship.author.toLowerCase());
|
|
296
|
+
|
|
297
|
+
if (isConnection && notifyLevel === 'all') {
|
|
298
|
+
const content = ship.content?.slice(0, 60) || 'something new';
|
|
299
|
+
showNotification(
|
|
300
|
+
`/vibe — @${ship.author} shipped! 🚀`,
|
|
301
|
+
content,
|
|
302
|
+
false, // no system sound
|
|
303
|
+
true // terminal bell (soft nudge)
|
|
304
|
+
);
|
|
305
|
+
newShips.push(ship);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
state.seenShipIds.push(shipId);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Trim old ship IDs (keep last 50)
|
|
312
|
+
if (state.seenShipIds.length > 50) {
|
|
313
|
+
state.seenShipIds = state.seenShipIds.slice(-50);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
state.lastCheck = now;
|
|
317
|
+
saveShipsState(state);
|
|
318
|
+
|
|
319
|
+
} catch (e) {
|
|
320
|
+
// Silent fail - notifications are best-effort
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return newShips;
|
|
324
|
+
}
|
|
325
|
+
|
|
239
326
|
/**
|
|
240
327
|
* Unified notification check - call from any tool
|
|
241
328
|
*/
|
|
@@ -255,6 +342,19 @@ async function checkAll(store) {
|
|
|
255
342
|
if (users.length > 0) {
|
|
256
343
|
checkPresence(users);
|
|
257
344
|
}
|
|
345
|
+
|
|
346
|
+
// Check for ships (only from connections)
|
|
347
|
+
// Get memory handles from local store
|
|
348
|
+
try {
|
|
349
|
+
const localStore = require('./store/local');
|
|
350
|
+
const memories = localStore.getAllThreadMemories ? localStore.getAllThreadMemories() : {};
|
|
351
|
+
const memoryHandles = Object.keys(memories).map(h => h.toLowerCase());
|
|
352
|
+
if (memoryHandles.length > 0) {
|
|
353
|
+
await checkShips(memoryHandles);
|
|
354
|
+
}
|
|
355
|
+
} catch (e) {
|
|
356
|
+
// Memory check is optional
|
|
357
|
+
}
|
|
258
358
|
} catch (e) {
|
|
259
359
|
// Silent fail - notifications are best-effort
|
|
260
360
|
}
|
|
@@ -264,6 +364,7 @@ module.exports = {
|
|
|
264
364
|
showNotification,
|
|
265
365
|
checkAndNotify,
|
|
266
366
|
checkPresence,
|
|
367
|
+
checkShips,
|
|
267
368
|
checkAll,
|
|
268
369
|
notify,
|
|
269
370
|
ringBell
|
package/package.json
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slashvibe-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"mcpName": "io.github.
|
|
3
|
+
"version": "0.3.13",
|
|
4
|
+
"mcpName": "io.github.vibecodinginc/vibe",
|
|
5
5
|
"description": "Social layer for Claude Code - DMs, presence, and connection between AI-assisted developers",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"slashvibe-mcp": "./index.js"
|
|
8
|
+
"slashvibe-mcp": "./index.js",
|
|
9
|
+
"vibe-setup": "./setup.js"
|
|
9
10
|
},
|
|
10
11
|
"scripts": {
|
|
11
|
-
"start": "node index.js"
|
|
12
|
+
"start": "node index.js",
|
|
13
|
+
"test": "node --test tools/*.test.js",
|
|
14
|
+
"test:context": "node --test tools/_work-context.test.js",
|
|
15
|
+
"lint": "eslint . --ext .js",
|
|
16
|
+
"lint:fix": "eslint . --ext .js --fix"
|
|
12
17
|
},
|
|
13
18
|
"keywords": [
|
|
14
19
|
"mcp",
|
|
@@ -23,22 +28,27 @@
|
|
|
23
28
|
"license": "MIT",
|
|
24
29
|
"repository": {
|
|
25
30
|
"type": "git",
|
|
26
|
-
"url": "git+https://github.com/
|
|
31
|
+
"url": "git+https://github.com/VibeCodingInc/vibe-mcp.git"
|
|
27
32
|
},
|
|
28
33
|
"homepage": "https://slashvibe.dev",
|
|
29
34
|
"bugs": {
|
|
30
|
-
"url": "https://github.com/
|
|
35
|
+
"url": "https://github.com/VibeCodingInc/vibe-mcp/issues"
|
|
31
36
|
},
|
|
32
37
|
"engines": {
|
|
33
38
|
"node": ">=18.0.0"
|
|
34
39
|
},
|
|
35
40
|
"files": [
|
|
36
41
|
"index.js",
|
|
42
|
+
"setup.js",
|
|
43
|
+
"auth-store.js",
|
|
44
|
+
"auto-update.js",
|
|
37
45
|
"config.js",
|
|
38
46
|
"crypto.js",
|
|
39
47
|
"discord.js",
|
|
40
48
|
"memory.js",
|
|
41
49
|
"notify.js",
|
|
50
|
+
"notification-emitter.js",
|
|
51
|
+
"analytics.js",
|
|
42
52
|
"presence.js",
|
|
43
53
|
"prompts.js",
|
|
44
54
|
"twitter.js",
|
|
@@ -46,9 +56,13 @@
|
|
|
46
56
|
"tools/",
|
|
47
57
|
"store/",
|
|
48
58
|
"protocol/",
|
|
59
|
+
"intelligence/",
|
|
60
|
+
"games/",
|
|
61
|
+
"bridges/",
|
|
49
62
|
"README.md"
|
|
50
63
|
],
|
|
51
64
|
"dependencies": {
|
|
52
|
-
"crossword-layout-generator": "^0.1.1"
|
|
65
|
+
"crossword-layout-generator": "^0.1.1",
|
|
66
|
+
"node-fetch": "^2.7.0"
|
|
53
67
|
}
|
|
54
68
|
}
|
package/prompts.js
CHANGED
|
@@ -68,7 +68,7 @@ function extractPatterns() {
|
|
|
68
68
|
|
|
69
69
|
for (const p of prompts) {
|
|
70
70
|
// Normalize: lowercase, replace @handles with @*, replace quoted strings with "*"
|
|
71
|
-
|
|
71
|
+
const normalized = p.prompt.toLowerCase()
|
|
72
72
|
.replace(/@\w+/g, '@*')
|
|
73
73
|
.replace(/"[^"]+"/g, '"*"')
|
|
74
74
|
.replace(/'[^']+'/g, "'*'")
|
package/protocol/index.js
CHANGED
|
@@ -101,10 +101,49 @@ const ACK_SCHEMA = {
|
|
|
101
101
|
}
|
|
102
102
|
};
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Artifact schema — For sharing artifacts in messages
|
|
106
|
+
*
|
|
107
|
+
* Example:
|
|
108
|
+
* {
|
|
109
|
+
* type: 'artifact',
|
|
110
|
+
* version: '0.1.0',
|
|
111
|
+
* artifactId: 'artifact_1768035804429_1651c037',
|
|
112
|
+
* slug: 'pizza-guide-abc123',
|
|
113
|
+
* title: 'Stan's North Beach Pizza Guide',
|
|
114
|
+
* template: 'guide',
|
|
115
|
+
* preview: 'Best pizza spots in North Beach...',
|
|
116
|
+
* url: 'https://slashvibe.dev/a/pizza-guide-abc123'
|
|
117
|
+
* }
|
|
118
|
+
*/
|
|
119
|
+
const ARTIFACT_SCHEMA = {
|
|
120
|
+
type: 'artifact',
|
|
121
|
+
required: ['artifactId', 'slug', 'title', 'template', 'url'],
|
|
122
|
+
validate: (payload) => {
|
|
123
|
+
if (!payload.artifactId || typeof payload.artifactId !== 'string') {
|
|
124
|
+
return { valid: false, error: 'Missing or invalid artifactId' };
|
|
125
|
+
}
|
|
126
|
+
if (!payload.slug || typeof payload.slug !== 'string') {
|
|
127
|
+
return { valid: false, error: 'Missing or invalid slug' };
|
|
128
|
+
}
|
|
129
|
+
if (!payload.title || typeof payload.title !== 'string') {
|
|
130
|
+
return { valid: false, error: 'Missing or invalid title' };
|
|
131
|
+
}
|
|
132
|
+
if (!['guide', 'learning', 'workspace'].includes(payload.template)) {
|
|
133
|
+
return { valid: false, error: 'Invalid template (must be: guide, learning, workspace)' };
|
|
134
|
+
}
|
|
135
|
+
if (!payload.url || typeof payload.url !== 'string') {
|
|
136
|
+
return { valid: false, error: 'Missing or invalid url' };
|
|
137
|
+
}
|
|
138
|
+
return { valid: true };
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
104
142
|
const SCHEMAS = {
|
|
105
143
|
game: GAME_SCHEMA,
|
|
106
144
|
handoff: HANDOFF_SCHEMA,
|
|
107
|
-
ack: ACK_SCHEMA
|
|
145
|
+
ack: ACK_SCHEMA,
|
|
146
|
+
artifact: ARTIFACT_SCHEMA
|
|
108
147
|
};
|
|
109
148
|
|
|
110
149
|
// ============ PROTOCOL FUNCTIONS ============
|
|
@@ -266,6 +305,10 @@ function formatPayload(payload) {
|
|
|
266
305
|
return formatHandoffPayload(payload);
|
|
267
306
|
case 'ack':
|
|
268
307
|
return formatAckPayload(payload);
|
|
308
|
+
case 'artifact':
|
|
309
|
+
return formatArtifactPayload(payload);
|
|
310
|
+
case 'code':
|
|
311
|
+
return formatCodePayload(payload);
|
|
269
312
|
default:
|
|
270
313
|
return `📦 _${payload.type} payload_`;
|
|
271
314
|
}
|
|
@@ -316,6 +359,117 @@ function formatAckPayload(payload) {
|
|
|
316
359
|
return `${icon} Acknowledged: ${payload.replyTo} (${status})`;
|
|
317
360
|
}
|
|
318
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Format a code snippet payload for display
|
|
364
|
+
*/
|
|
365
|
+
function formatCodePayload(payload) {
|
|
366
|
+
const lang = payload.language || '';
|
|
367
|
+
const filename = payload.filename || null;
|
|
368
|
+
const code = payload.code || '';
|
|
369
|
+
const description = payload.description || null;
|
|
370
|
+
|
|
371
|
+
let display = '📝 **Code Snippet**';
|
|
372
|
+
if (filename) {
|
|
373
|
+
display += ` — \`${filename}\``;
|
|
374
|
+
}
|
|
375
|
+
if (lang) {
|
|
376
|
+
display += ` (${lang})`;
|
|
377
|
+
}
|
|
378
|
+
display += '\n';
|
|
379
|
+
|
|
380
|
+
if (description) {
|
|
381
|
+
display += `> ${description}\n`;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
display += `\`\`\`${lang}\n${code}\n\`\`\``;
|
|
385
|
+
|
|
386
|
+
return display;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Create a code snippet payload
|
|
391
|
+
* @param {string} code - The code content
|
|
392
|
+
* @param {object} options - Options
|
|
393
|
+
* @param {string} [options.language] - Programming language
|
|
394
|
+
* @param {string} [options.filename] - Original filename
|
|
395
|
+
* @param {string} [options.description] - Brief description
|
|
396
|
+
*/
|
|
397
|
+
function createCodePayload(code, options = {}) {
|
|
398
|
+
return {
|
|
399
|
+
type: 'code',
|
|
400
|
+
version: PROTOCOL_VERSION,
|
|
401
|
+
code,
|
|
402
|
+
language: options.language || detectLanguage(code),
|
|
403
|
+
filename: options.filename || null,
|
|
404
|
+
description: options.description || null,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Simple language detection based on content patterns
|
|
410
|
+
*/
|
|
411
|
+
function detectLanguage(code) {
|
|
412
|
+
if (!code) return '';
|
|
413
|
+
|
|
414
|
+
// Check for common patterns
|
|
415
|
+
if (code.includes('import React') || code.includes('useState') || code.includes('useEffect')) return 'jsx';
|
|
416
|
+
if (code.includes('import ') && code.includes(' from ')) return 'javascript';
|
|
417
|
+
if (code.includes('async function') || code.includes('await ')) return 'javascript';
|
|
418
|
+
if (code.includes('def ') && code.includes(':')) return 'python';
|
|
419
|
+
if (code.includes('func ') && code.includes('()')) return 'go';
|
|
420
|
+
if (code.includes('fn ') && code.includes('->')) return 'rust';
|
|
421
|
+
if (code.includes('SELECT ') || code.includes('INSERT INTO')) return 'sql';
|
|
422
|
+
if (code.includes('<!DOCTYPE') || code.includes('<html')) return 'html';
|
|
423
|
+
if (code.includes('{') && code.includes(':') && code.includes(';')) return 'css';
|
|
424
|
+
if (code.startsWith('{') && code.endsWith('}')) return 'json';
|
|
425
|
+
if (code.startsWith('#!') && code.includes('/bin/')) return 'bash';
|
|
426
|
+
|
|
427
|
+
return '';
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function formatArtifactPayload(payload) {
|
|
431
|
+
const template = payload.template || 'artifact';
|
|
432
|
+
const templateIcon = template === 'guide' ? '📘' : template === 'learning' ? '💡' : template === 'workspace' ? '🗂️' : '📦';
|
|
433
|
+
|
|
434
|
+
let display = `${templateIcon} **${payload.title}**\n`;
|
|
435
|
+
|
|
436
|
+
if (payload.preview) {
|
|
437
|
+
const preview = payload.preview.length > 120 ? payload.preview.substring(0, 120) + '...' : payload.preview;
|
|
438
|
+
display += `> ${preview}\n\n`;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
display += `🔗 [View artifact](${payload.url})`;
|
|
442
|
+
|
|
443
|
+
return display;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ============ ARTIFACT HELPERS ============
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Create an artifact card payload
|
|
450
|
+
* @param {Object} artifact - Artifact object from API
|
|
451
|
+
* @returns {Object} - Artifact payload
|
|
452
|
+
*/
|
|
453
|
+
function createArtifactPayload(artifact) {
|
|
454
|
+
// Extract preview from first paragraph block
|
|
455
|
+
let preview = '';
|
|
456
|
+
if (artifact.content && artifact.content.blocks) {
|
|
457
|
+
const firstPara = artifact.content.blocks.find(b => b.type === 'paragraph');
|
|
458
|
+
if (firstPara && firstPara.markdown) {
|
|
459
|
+
preview = firstPara.markdown.substring(0, 150);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return createPayload('artifact', {
|
|
464
|
+
artifactId: artifact.id,
|
|
465
|
+
slug: artifact.slug,
|
|
466
|
+
title: artifact.title,
|
|
467
|
+
template: artifact.template,
|
|
468
|
+
preview: preview || undefined,
|
|
469
|
+
url: `https://slashvibe.dev/a/${artifact.slug}`
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
319
473
|
module.exports = {
|
|
320
474
|
PROTOCOL_VERSION,
|
|
321
475
|
|
|
@@ -335,6 +489,12 @@ module.exports = {
|
|
|
335
489
|
// Ack helpers
|
|
336
490
|
createAckPayload,
|
|
337
491
|
|
|
492
|
+
// Artifact helpers
|
|
493
|
+
createArtifactPayload,
|
|
494
|
+
|
|
495
|
+
// Code helpers
|
|
496
|
+
createCodePayload,
|
|
497
|
+
|
|
338
498
|
// Schemas (for extension)
|
|
339
499
|
SCHEMAS
|
|
340
500
|
};
|