slashvibe-mcp 0.2.2 → 0.2.4
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/config.js +174 -3
- package/index.js +163 -34
- 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/notification-emitter.js +77 -0
- package/notify.js +141 -18
- package/package.json +14 -6
- package/presence.js +5 -1
- package/protocol/index.js +88 -1
- package/protocol/telegram-commands.js +199 -0
- package/store/api.js +469 -29
- package/store/index.js +7 -7
- package/store/local.js +67 -11
- package/store/profiles.js +435 -0
- package/store/reservations.js +321 -0
- package/store/skills.js +378 -0
- package/tools/_actions.js +491 -22
- package/tools/_connection-queue.js +257 -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/_work-context.js +338 -0
- package/tools/_work-context.manual-test.js +199 -0
- package/tools/_work-context.test.js +260 -0
- package/tools/admin-inbox.js +218 -0
- package/tools/agent-treasury.js +288 -0
- package/tools/agents.js +122 -0
- package/tools/analytics.js +191 -0
- package/tools/approve.js +197 -0
- package/tools/arcade.js +173 -0
- package/tools/artifact-create.js +247 -0
- package/tools/artifact-view.js +174 -0
- package/tools/artifacts-price.js +107 -0
- package/tools/ask-expert.js +160 -0
- package/tools/auto-suggest-connections.js +304 -0
- package/tools/away.js +68 -0
- package/tools/back.js +51 -0
- package/tools/become-expert.js +150 -0
- package/tools/bootstrap-skills.js +231 -0
- package/tools/bridge-dashboard.js +342 -0
- package/tools/bridge-health.js +400 -0
- package/tools/bridge-live.js +384 -0
- package/tools/bridges.js +383 -0
- package/tools/broadcast.js +286 -0
- package/tools/bye.js +4 -0
- package/tools/chat.js +202 -0
- package/tools/collaborative-drawing.js +286 -0
- package/tools/colorguess.js +281 -0
- package/tools/crossword.js +369 -0
- package/tools/discover-insights.js +379 -0
- package/tools/discover-momentum.js +256 -0
- package/tools/discover.js +675 -0
- package/tools/discovery-analytics.js +345 -0
- package/tools/discovery-auto-suggest.js +275 -0
- package/tools/discovery-bootstrap.js +267 -0
- package/tools/discovery-daily.js +375 -0
- package/tools/discovery-dashboard.js +385 -0
- package/tools/discovery-digest.js +314 -0
- package/tools/discovery-hub.js +357 -0
- package/tools/discovery-insights.js +384 -0
- package/tools/discovery-momentum.js +281 -0
- package/tools/discovery-monitor.js +319 -0
- package/tools/discovery-proactive.js +300 -0
- package/tools/dm.js +84 -14
- package/tools/draw.js +317 -0
- package/tools/drawing.js +310 -0
- package/tools/earnings.js +126 -0
- package/tools/echo.js +16 -0
- package/tools/farcaster.js +307 -0
- package/tools/feed.js +215 -0
- package/tools/follow.js +224 -0
- package/tools/friends.js +192 -0
- package/tools/game.js +218 -110
- package/tools/games-catalog.js +376 -0
- package/tools/games.js +313 -0
- package/tools/genesis.js +233 -0
- package/tools/gig-browse.js +206 -0
- package/tools/gig-complete.js +139 -0
- package/tools/guessnumber.js +194 -0
- package/tools/hangman.js +129 -0
- package/tools/help.js +269 -0
- package/tools/idea.js +217 -0
- package/tools/inbox.js +291 -25
- package/tools/init.js +657 -33
- package/tools/insights.js +123 -0
- package/tools/invite.js +142 -21
- 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 +275 -0
- package/tools/multiplayer-tictactoe.js +303 -0
- package/tools/mute.js +97 -0
- package/tools/notifications.js +415 -0
- package/tools/observe.js +200 -0
- package/tools/onboarding.js +147 -0
- package/tools/open.js +52 -3
- package/tools/party-game.js +314 -0
- package/tools/plan.js +225 -0
- package/tools/presence-agent.js +167 -0
- package/tools/profile.js +219 -0
- package/tools/proof-of-work.js +139 -0
- package/tools/pulse.js +218 -0
- package/tools/react.js +4 -0
- package/tools/release.js +83 -0
- package/tools/report.js +109 -0
- package/tools/reputation.js +175 -0
- package/tools/request.js +231 -0
- package/tools/reservations.js +116 -0
- package/tools/reserve.js +111 -0
- package/tools/riddle.js +240 -0
- package/tools/run-bootstrap.js +69 -0
- package/tools/schedule.js +367 -0
- package/tools/session.js +420 -0
- package/tools/session_price.js +128 -0
- package/tools/settings.js +200 -0
- package/tools/ship.js +188 -0
- package/tools/shipback.js +326 -0
- package/tools/skills-analytics.js +349 -0
- package/tools/skills-bootstrap.js +301 -0
- package/tools/skills-dashboard.js +268 -0
- package/tools/skills-exchange.js +342 -0
- package/tools/skills.js +380 -0
- package/tools/smart-intro.js +353 -0
- package/tools/social-inbox.js +326 -69
- package/tools/social-post.js +251 -66
- package/tools/social-processor.js +445 -0
- package/tools/solo-game.js +390 -0
- package/tools/start.js +296 -81
- package/tools/status.js +53 -6
- package/tools/storybuilder.js +331 -0
- package/tools/stuck.js +297 -0
- package/tools/subscribe.js +148 -0
- package/tools/subscriptions.js +134 -0
- package/tools/suggest-tags.js +184 -0
- package/tools/tag-suggestions.js +257 -0
- package/tools/telegram-bot.js +183 -0
- package/tools/telegram-setup.js +214 -0
- package/tools/tictactoe.js +155 -0
- package/tools/tip.js +120 -0
- package/tools/token.js +103 -0
- package/tools/twentyquestions.js +143 -0
- package/tools/update.js +1 -1
- package/tools/wallet.js +127 -0
- package/tools/watch.js +157 -0
- package/tools/webhook-test.js +388 -0
- package/tools/who.js +118 -25
- package/tools/withdraw.js +145 -0
- package/tools/wordassociation.js +247 -0
- package/tools/work-summary.js +96 -0
- package/tools/workshop-buddy.js +394 -0
- package/tools/workshop.js +327 -0
- package/version.json +12 -3
- package/tools/board.js +0 -130
|
@@ -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
|
@@ -34,15 +34,28 @@ function saveNotifyState(state) {
|
|
|
34
34
|
} catch (e) {}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Ring terminal bell (BEL character)
|
|
39
|
+
* Ambient, non-intrusive alert
|
|
40
|
+
*/
|
|
41
|
+
function ringBell() {
|
|
42
|
+
process.stderr.write('\x07');
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
/**
|
|
38
46
|
* Show a macOS notification
|
|
39
47
|
*/
|
|
40
|
-
function showNotification(title, message, sound = false) {
|
|
48
|
+
function showNotification(title, message, sound = false, bell = true) {
|
|
41
49
|
if (os.platform() !== 'darwin') {
|
|
42
50
|
// Only macOS supported for now
|
|
43
51
|
return;
|
|
44
52
|
}
|
|
45
53
|
|
|
54
|
+
// Ring terminal bell for all notifications
|
|
55
|
+
if (bell) {
|
|
56
|
+
ringBell();
|
|
57
|
+
}
|
|
58
|
+
|
|
46
59
|
// Escape quotes for osascript
|
|
47
60
|
const safeTitle = title.replace(/"/g, '\\"');
|
|
48
61
|
const safeMessage = message.replace(/"/g, '\\"');
|
|
@@ -62,8 +75,14 @@ function showNotification(title, message, sound = false) {
|
|
|
62
75
|
/**
|
|
63
76
|
* Check for messages that need escalation
|
|
64
77
|
* Called periodically or on inbox check
|
|
78
|
+
* Respects notification settings: all | mentions | off
|
|
65
79
|
*/
|
|
66
80
|
async function checkAndNotify(inbox) {
|
|
81
|
+
const notifyLevel = config.getNotifications();
|
|
82
|
+
|
|
83
|
+
// If notifications are off, skip entirely
|
|
84
|
+
if (notifyLevel === 'off') return false;
|
|
85
|
+
|
|
67
86
|
const state = loadNotifyState();
|
|
68
87
|
const now = Date.now();
|
|
69
88
|
const FIVE_MINUTES = 5 * 60 * 1000;
|
|
@@ -84,25 +103,25 @@ async function checkAndNotify(inbox) {
|
|
|
84
103
|
let shouldNotify = false;
|
|
85
104
|
let reason = '';
|
|
86
105
|
|
|
87
|
-
// Rule 1:
|
|
88
|
-
if (age > FIVE_MINUTES) {
|
|
89
|
-
shouldNotify = true;
|
|
90
|
-
reason = 'unread';
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Rule 2: Direct mention in message
|
|
106
|
+
// Rule 1: Direct mention in message (always if not "off")
|
|
94
107
|
const myHandle = config.getHandle();
|
|
95
108
|
if (myHandle && msg.text && msg.text.toLowerCase().includes(`@${myHandle}`)) {
|
|
96
109
|
shouldNotify = true;
|
|
97
110
|
reason = 'mention';
|
|
98
111
|
}
|
|
99
112
|
|
|
100
|
-
// Rule
|
|
113
|
+
// Rule 2: Handshake/consent request (always if not "off")
|
|
101
114
|
if (msg.payload?.type === 'handshake') {
|
|
102
115
|
shouldNotify = true;
|
|
103
116
|
reason = 'handshake';
|
|
104
117
|
}
|
|
105
118
|
|
|
119
|
+
// Rule 3: Unread > 5 minutes (only in "all" mode)
|
|
120
|
+
if (notifyLevel === 'all' && age > FIVE_MINUTES) {
|
|
121
|
+
shouldNotify = true;
|
|
122
|
+
reason = reason || 'unread'; // Don't override mention/handshake
|
|
123
|
+
}
|
|
124
|
+
|
|
106
125
|
if (shouldNotify) {
|
|
107
126
|
const preview = msg.text?.slice(0, 50) || '(no preview)';
|
|
108
127
|
showNotification(
|
|
@@ -157,8 +176,11 @@ function savePresenceState(state) {
|
|
|
157
176
|
/**
|
|
158
177
|
* Check for new online users and notify
|
|
159
178
|
* Returns handles that just came online
|
|
179
|
+
* Only notifies in "all" mode (presence is lower priority)
|
|
160
180
|
*/
|
|
161
181
|
function checkPresence(activeUsers) {
|
|
182
|
+
const notifyLevel = config.getNotifications();
|
|
183
|
+
|
|
162
184
|
const state = loadPresenceState();
|
|
163
185
|
const now = Date.now();
|
|
164
186
|
const COOLDOWN = 30 * 60 * 1000; // Don't re-notify about same person for 30 min
|
|
@@ -183,17 +205,20 @@ function checkPresence(activeUsers) {
|
|
|
183
205
|
if (userActive) {
|
|
184
206
|
justJoined.push(user);
|
|
185
207
|
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
208
|
+
// Only show notification in "all" mode (presence is lower priority)
|
|
209
|
+
if (notifyLevel === 'all') {
|
|
210
|
+
const context = user.note || user.one_liner || 'just joined';
|
|
211
|
+
showNotification(
|
|
212
|
+
`/vibe — @${user.handle} is here`,
|
|
213
|
+
context,
|
|
214
|
+
false, // no system sound
|
|
215
|
+
false // no terminal bell for presence (too noisy)
|
|
216
|
+
);
|
|
217
|
+
}
|
|
193
218
|
}
|
|
194
219
|
}
|
|
195
220
|
|
|
196
|
-
// Update last seen
|
|
221
|
+
// Update last seen (always track, even if not notifying)
|
|
197
222
|
state.seenHandles[user.handle] = now;
|
|
198
223
|
}
|
|
199
224
|
|
|
@@ -211,6 +236,89 @@ function checkPresence(activeUsers) {
|
|
|
211
236
|
return justJoined;
|
|
212
237
|
}
|
|
213
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Notify about new ships from connections (FOMO/retention driver)
|
|
241
|
+
*/
|
|
242
|
+
const SHIPS_STATE_FILE = path.join(config.VIBE_DIR, '.ships_state.json');
|
|
243
|
+
|
|
244
|
+
function loadShipsState() {
|
|
245
|
+
try {
|
|
246
|
+
if (fs.existsSync(SHIPS_STATE_FILE)) {
|
|
247
|
+
return JSON.parse(fs.readFileSync(SHIPS_STATE_FILE, 'utf8'));
|
|
248
|
+
}
|
|
249
|
+
} catch (e) {}
|
|
250
|
+
return { seenShipIds: [], lastCheck: null };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function saveShipsState(state) {
|
|
254
|
+
try {
|
|
255
|
+
fs.writeFileSync(SHIPS_STATE_FILE, JSON.stringify(state, null, 2));
|
|
256
|
+
} catch (e) {}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Check for new ships and notify
|
|
261
|
+
* Only notifies about ships from people in memory (connections)
|
|
262
|
+
*/
|
|
263
|
+
async function checkShips(memoryHandles = []) {
|
|
264
|
+
const notifyLevel = config.getNotifications();
|
|
265
|
+
if (notifyLevel === 'off') return [];
|
|
266
|
+
|
|
267
|
+
const state = loadShipsState();
|
|
268
|
+
const now = Date.now();
|
|
269
|
+
const myHandle = config.getHandle();
|
|
270
|
+
const newShips = [];
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
// Fetch recent ships from board API
|
|
274
|
+
const apiUrl = config.getApiUrl();
|
|
275
|
+
const response = await fetch(`${apiUrl}/api/board?limit=10&category=shipped`);
|
|
276
|
+
const data = await response.json();
|
|
277
|
+
const ships = data.entries || [];
|
|
278
|
+
|
|
279
|
+
for (const ship of ships) {
|
|
280
|
+
// Skip if already seen
|
|
281
|
+
const shipId = ship.id || `${ship.author}-${ship.timestamp}`;
|
|
282
|
+
if (state.seenShipIds.includes(shipId)) continue;
|
|
283
|
+
|
|
284
|
+
// Skip our own ships
|
|
285
|
+
if (ship.author === myHandle) {
|
|
286
|
+
state.seenShipIds.push(shipId);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Only notify about ships from connections (people in memory)
|
|
291
|
+
const isConnection = memoryHandles.includes(ship.author.toLowerCase());
|
|
292
|
+
|
|
293
|
+
if (isConnection && notifyLevel === 'all') {
|
|
294
|
+
const content = ship.content?.slice(0, 60) || 'something new';
|
|
295
|
+
showNotification(
|
|
296
|
+
`/vibe — @${ship.author} shipped! 🚀`,
|
|
297
|
+
content,
|
|
298
|
+
false, // no system sound
|
|
299
|
+
true // terminal bell (soft nudge)
|
|
300
|
+
);
|
|
301
|
+
newShips.push(ship);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
state.seenShipIds.push(shipId);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Trim old ship IDs (keep last 50)
|
|
308
|
+
if (state.seenShipIds.length > 50) {
|
|
309
|
+
state.seenShipIds = state.seenShipIds.slice(-50);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
state.lastCheck = now;
|
|
313
|
+
saveShipsState(state);
|
|
314
|
+
|
|
315
|
+
} catch (e) {
|
|
316
|
+
// Silent fail - notifications are best-effort
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return newShips;
|
|
320
|
+
}
|
|
321
|
+
|
|
214
322
|
/**
|
|
215
323
|
* Unified notification check - call from any tool
|
|
216
324
|
*/
|
|
@@ -230,6 +338,19 @@ async function checkAll(store) {
|
|
|
230
338
|
if (users.length > 0) {
|
|
231
339
|
checkPresence(users);
|
|
232
340
|
}
|
|
341
|
+
|
|
342
|
+
// Check for ships (only from connections)
|
|
343
|
+
// Get memory handles from local store
|
|
344
|
+
try {
|
|
345
|
+
const localStore = require('./store/local');
|
|
346
|
+
const memories = localStore.getAllThreadMemories ? localStore.getAllThreadMemories() : {};
|
|
347
|
+
const memoryHandles = Object.keys(memories).map(h => h.toLowerCase());
|
|
348
|
+
if (memoryHandles.length > 0) {
|
|
349
|
+
await checkShips(memoryHandles);
|
|
350
|
+
}
|
|
351
|
+
} catch (e) {
|
|
352
|
+
// Memory check is optional
|
|
353
|
+
}
|
|
233
354
|
} catch (e) {
|
|
234
355
|
// Silent fail - notifications are best-effort
|
|
235
356
|
}
|
|
@@ -239,6 +360,8 @@ module.exports = {
|
|
|
239
360
|
showNotification,
|
|
240
361
|
checkAndNotify,
|
|
241
362
|
checkPresence,
|
|
363
|
+
checkShips,
|
|
242
364
|
checkAll,
|
|
243
|
-
notify
|
|
365
|
+
notify,
|
|
366
|
+
ringBell
|
|
244
367
|
};
|
package/package.json
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slashvibe-mcp",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"mcpName": "io.github.
|
|
3
|
+
"version": "0.2.4",
|
|
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
8
|
"slashvibe-mcp": "./index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"start": "node index.js"
|
|
11
|
+
"start": "node index.js",
|
|
12
|
+
"test": "node --test tools/*.test.js",
|
|
13
|
+
"test:context": "node --test tools/_work-context.test.js"
|
|
12
14
|
},
|
|
13
15
|
"keywords": [
|
|
14
16
|
"mcp",
|
|
@@ -23,11 +25,11 @@
|
|
|
23
25
|
"license": "MIT",
|
|
24
26
|
"repository": {
|
|
25
27
|
"type": "git",
|
|
26
|
-
"url": "git+https://github.com/
|
|
28
|
+
"url": "git+https://github.com/VibeCodingInc/vibe-mcp.git"
|
|
27
29
|
},
|
|
28
30
|
"homepage": "https://slashvibe.dev",
|
|
29
31
|
"bugs": {
|
|
30
|
-
"url": "https://github.com/
|
|
32
|
+
"url": "https://github.com/VibeCodingInc/vibe-mcp/issues"
|
|
31
33
|
},
|
|
32
34
|
"engines": {
|
|
33
35
|
"node": ">=18.0.0"
|
|
@@ -39,6 +41,8 @@
|
|
|
39
41
|
"discord.js",
|
|
40
42
|
"memory.js",
|
|
41
43
|
"notify.js",
|
|
44
|
+
"notification-emitter.js",
|
|
45
|
+
"analytics.js",
|
|
42
46
|
"presence.js",
|
|
43
47
|
"prompts.js",
|
|
44
48
|
"twitter.js",
|
|
@@ -46,6 +50,10 @@
|
|
|
46
50
|
"tools/",
|
|
47
51
|
"store/",
|
|
48
52
|
"protocol/",
|
|
53
|
+
"intelligence/",
|
|
49
54
|
"README.md"
|
|
50
|
-
]
|
|
55
|
+
],
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"crossword-layout-generator": "^0.1.1"
|
|
58
|
+
}
|
|
51
59
|
}
|
package/presence.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const config = require('./config');
|
|
10
10
|
const store = require('./store');
|
|
11
|
+
const notify = require('./notify');
|
|
11
12
|
|
|
12
13
|
let heartbeatInterval = null;
|
|
13
14
|
let sessionInitialized = false;
|
|
@@ -52,7 +53,7 @@ async function initSession() {
|
|
|
52
53
|
sendHeartbeat();
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
function sendHeartbeat() {
|
|
56
|
+
async function sendHeartbeat() {
|
|
56
57
|
if (!config.isInitialized()) return;
|
|
57
58
|
|
|
58
59
|
// Use session-aware getters (prefer session identity over shared config)
|
|
@@ -60,6 +61,9 @@ function sendHeartbeat() {
|
|
|
60
61
|
const one_liner = config.getOneLiner();
|
|
61
62
|
if (handle) {
|
|
62
63
|
store.heartbeat(handle, one_liner || '');
|
|
64
|
+
|
|
65
|
+
// Check for notifications (runs in background, non-blocking)
|
|
66
|
+
notify.checkAll(store).catch(() => {});
|
|
63
67
|
}
|
|
64
68
|
}
|
|
65
69
|
|
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,8 @@ function formatPayload(payload) {
|
|
|
266
305
|
return formatHandoffPayload(payload);
|
|
267
306
|
case 'ack':
|
|
268
307
|
return formatAckPayload(payload);
|
|
308
|
+
case 'artifact':
|
|
309
|
+
return formatArtifactPayload(payload);
|
|
269
310
|
default:
|
|
270
311
|
return `📦 _${payload.type} payload_`;
|
|
271
312
|
}
|
|
@@ -316,6 +357,49 @@ function formatAckPayload(payload) {
|
|
|
316
357
|
return `${icon} Acknowledged: ${payload.replyTo} (${status})`;
|
|
317
358
|
}
|
|
318
359
|
|
|
360
|
+
function formatArtifactPayload(payload) {
|
|
361
|
+
const template = payload.template || 'artifact';
|
|
362
|
+
const templateIcon = template === 'guide' ? '📘' : template === 'learning' ? '💡' : template === 'workspace' ? '🗂️' : '📦';
|
|
363
|
+
|
|
364
|
+
let display = `${templateIcon} **${payload.title}**\n`;
|
|
365
|
+
|
|
366
|
+
if (payload.preview) {
|
|
367
|
+
const preview = payload.preview.length > 120 ? payload.preview.substring(0, 120) + '...' : payload.preview;
|
|
368
|
+
display += `> ${preview}\n\n`;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
display += `🔗 [View artifact](${payload.url})`;
|
|
372
|
+
|
|
373
|
+
return display;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ============ ARTIFACT HELPERS ============
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Create an artifact card payload
|
|
380
|
+
* @param {Object} artifact - Artifact object from API
|
|
381
|
+
* @returns {Object} - Artifact payload
|
|
382
|
+
*/
|
|
383
|
+
function createArtifactPayload(artifact) {
|
|
384
|
+
// Extract preview from first paragraph block
|
|
385
|
+
let preview = '';
|
|
386
|
+
if (artifact.content && artifact.content.blocks) {
|
|
387
|
+
const firstPara = artifact.content.blocks.find(b => b.type === 'paragraph');
|
|
388
|
+
if (firstPara && firstPara.markdown) {
|
|
389
|
+
preview = firstPara.markdown.substring(0, 150);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return createPayload('artifact', {
|
|
394
|
+
artifactId: artifact.id,
|
|
395
|
+
slug: artifact.slug,
|
|
396
|
+
title: artifact.title,
|
|
397
|
+
template: artifact.template,
|
|
398
|
+
preview: preview || undefined,
|
|
399
|
+
url: `https://slashvibe.dev/a/${artifact.slug}`
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
319
403
|
module.exports = {
|
|
320
404
|
PROTOCOL_VERSION,
|
|
321
405
|
|
|
@@ -335,6 +419,9 @@ module.exports = {
|
|
|
335
419
|
// Ack helpers
|
|
336
420
|
createAckPayload,
|
|
337
421
|
|
|
422
|
+
// Artifact helpers
|
|
423
|
+
createArtifactPayload,
|
|
424
|
+
|
|
338
425
|
// Schemas (for extension)
|
|
339
426
|
SCHEMAS
|
|
340
427
|
};
|