slashvibe-mcp 0.5.10 → 0.5.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slashvibe-mcp",
3
- "version": "0.5.10",
3
+ "version": "0.5.11",
4
4
  "mcpName": "io.github.vibecodinginc/vibe",
5
5
  "description": "Social layer for Claude Code - DMs, presence, Matrix multiplayer rooms, and connection between AI-assisted developers",
6
6
  "main": "index.js",
package/store/api.js CHANGED
@@ -352,7 +352,9 @@ async function getActiveUsers() {
352
352
  awayAt: u.awayAt || u.context?.awayAt || null,
353
353
  // v2 additions
354
354
  isLive: u.isLive || false,
355
- isAgent: u.isAgent || false
355
+ isAgent: u.isAgent || false,
356
+ // Presence texture (server-enriched): most-recent ship
357
+ lastShip: u.lastShip || null
356
358
  }));
357
359
 
358
360
  // Sync presence data to local profiles (non-blocking)
package/tools/_shared.js CHANGED
@@ -202,6 +202,88 @@ async function fetchRelevantUsers(handle, context = 'dm_suggest', limit = 5) {
202
202
  }
203
203
  }
204
204
 
205
+ // ─────────────────────────────────────────────────────────────
206
+ // First-DM activation (dormant users)
207
+ // ─────────────────────────────────────────────────────────────
208
+
209
+ /**
210
+ * Has this user ever sent a DM?
211
+ * Durable signal: a persisted `firstDmSent` flag (set on first successful send
212
+ * via markFirstDmSent), seeded by the presence of any conversation memory —
213
+ * existing messagers almost always have memory threads, so they're pre-excluded
214
+ * and never see the nudge. Self-heals: the moment they send one DM, the flag
215
+ * flips and the nudge is gone for good.
216
+ *
217
+ * @param {Array} threads - result of memory.listThreads()
218
+ * @returns {boolean}
219
+ */
220
+ function hasEverMessaged(threads) {
221
+ try {
222
+ const cfg = config.load();
223
+ if (cfg && cfg.firstDmSent) return true;
224
+ } catch (e) {}
225
+ return Array.isArray(threads) && threads.length > 0;
226
+ }
227
+
228
+ /**
229
+ * Persist the "has sent a first DM" flag so the activation nudge stops.
230
+ * Called from dm.js on a successful send. Cheap, idempotent, best-effort.
231
+ */
232
+ function markFirstDmSent() {
233
+ try {
234
+ const cfg = config.load();
235
+ if (!cfg || !cfg.firstDmSent) config.save({ firstDmSent: true });
236
+ } catch (e) {}
237
+ }
238
+
239
+ /**
240
+ * Pick the most-recently-active real human from a presence list, with a
241
+ * ready-to-paste opener. Filters out agents (is_agent) and self. Warms the
242
+ * opener from the target's most recent ship (lastShip) when available.
243
+ *
244
+ * @param {Array} others - active users (already excludes self by convention)
245
+ * @returns {{handle: string, opener: string}|null}
246
+ */
247
+ function pickDormantTarget(others) {
248
+ const humans = (others || []).filter((u) => u && u.handle && !u.is_agent);
249
+ if (humans.length === 0) return null;
250
+ humans.sort(
251
+ (a, b) => new Date(b.lastSeen || b.last_seen || 0) - new Date(a.lastSeen || a.last_seen || 0)
252
+ );
253
+ const pick = humans[0];
254
+ // The opener is pasted inside a double-quoted `vibe dm @x "..."` command, so
255
+ // strip any double quotes/backslashes the ship text itself contains (ship
256
+ // content is arbitrary) and single-quote the snippet — otherwise an embedded
257
+ // quote closes the command early and breaks the paste.
258
+ const rawShip = pick.lastShip && pick.lastShip.what;
259
+ const shipped = rawShip ? String(rawShip).replace(/["\\]/g, '').trim().slice(0, 48) : '';
260
+ const opener = shipped
261
+ ? `hey! saw you shipped '${shipped}' — what are you building?`
262
+ : `hey! what are you building?`;
263
+ return { handle: pick.handle, opener };
264
+ }
265
+
266
+ /**
267
+ * Render the first-DM activation nudge, or '' when it shouldn't show.
268
+ * Shows only for users who have never sent a DM, and only when a real human is
269
+ * around right now to receive it. This is the returning-user twin of the
270
+ * new-user welcome first-message pull — it reaches the dormant existing users
271
+ * where the actual funnel is.
272
+ *
273
+ * @param {Array} others - active users (excludes self)
274
+ * @param {Array} threads - memory.listThreads()
275
+ * @returns {string} markdown block (leading blank line) or ''
276
+ */
277
+ function firstDmNudge(others, threads) {
278
+ if (hasEverMessaged(threads)) return '';
279
+ const target = pickDormantTarget(others);
280
+ if (!target) return '';
281
+ return (
282
+ `\n\n**👋 You haven't messaged anyone yet — @${target.handle} is around right now:**` +
283
+ `\n vibe dm @${target.handle} "${target.opener}"`
284
+ );
285
+ }
286
+
205
287
  // ─────────────────────────────────────────────────────────────
206
288
  // Exports
207
289
  // ─────────────────────────────────────────────────────────────
@@ -230,5 +312,11 @@ module.exports = {
230
312
  error,
231
313
 
232
314
  // API
233
- fetchRelevantUsers
315
+ fetchRelevantUsers,
316
+
317
+ // First-DM activation (dormant users)
318
+ hasEverMessaged,
319
+ markFirstDmSent,
320
+ pickDormantTarget,
321
+ firstDmNudge
234
322
  };
package/tools/dm.js CHANGED
@@ -8,7 +8,7 @@ const memory = require('../memory');
8
8
  const userProfiles = require('../store/profiles');
9
9
  const patterns = require('../intelligence/patterns');
10
10
  const { trackMessage, checkBurst } = require('./summarize');
11
- const { requireInit, normalizeHandle, truncate, warning, fetchRelevantUsers } = require('./_shared');
11
+ const { requireInit, normalizeHandle, truncate, warning, fetchRelevantUsers, markFirstDmSent } = require('./_shared');
12
12
  const { actions, formatActions } = require('./_actions');
13
13
 
14
14
  const definition = {
@@ -110,6 +110,10 @@ async function handler(args) {
110
110
  };
111
111
  }
112
112
 
113
+ // Mark that this user has sent a DM — silences the first-DM activation nudge
114
+ // (vibe start / vibe who) for good. Durable, idempotent, best-effort.
115
+ markFirstDmSent();
116
+
113
117
  // Log social pattern (quietly, in background)
114
118
  patterns.logMessageSent(them);
115
119
 
package/tools/start.js CHANGED
@@ -17,6 +17,7 @@ const memory = require('../memory');
17
17
  const notify = require('../notify');
18
18
  const patterns = require('../intelligence/patterns');
19
19
  const { actions, formatActions } = require('./_actions');
20
+ const { firstDmNudge } = require('./_shared');
20
21
  const init = require('./init');
21
22
  const { gatherWithTimeout } = require('./_work-context');
22
23
 
@@ -330,6 +331,11 @@ async function handler(args) {
330
331
  }
331
332
  }
332
333
 
334
+ // First-DM activation nudge (dormant users only): if this user has never sent
335
+ // a DM, point them at a real human who's around right now with a ready opener.
336
+ // Gated inside firstDmNudge so we never nag people who already message.
337
+ display += firstDmNudge(others, threads);
338
+
333
339
  // Add unread messages section (if any)
334
340
  if (unreadCount > 0) {
335
341
  const unreadSenders = inboxThreads.filter(t => t.unread > 0);
package/tools/who.js CHANGED
@@ -10,7 +10,8 @@
10
10
  const config = require('../config');
11
11
  const store = require('../store');
12
12
  const notify = require('../notify');
13
- const { formatTimeAgo, requireInit } = require('./_shared');
13
+ const { formatTimeAgo, requireInit, firstDmNudge } = require('./_shared');
14
+ const memory = require('../memory');
14
15
  const { actions, formatActions } = require('./_actions');
15
16
  const { enhanceUsersWithInference } = require('../intelligence/infer');
16
17
  const { getTopSerendipity, getAllSerendipity } = require('../intelligence/serendipity');
@@ -225,10 +226,25 @@ _Check back in a bit — builders come and go._`
225
226
  display += ` ${operatorTag}\n`;
226
227
  }
227
228
  display += ` ${activity}\n`;
229
+ // Presence texture: what they last shipped
230
+ if (u.lastShip && u.lastShip.what) {
231
+ display += ` _last shipped: "${u.lastShip.what}" ${u.lastShip.ago}_\n`;
232
+ }
228
233
  display += ` _${timeAgo}_\n\n`;
229
234
  });
230
235
  }
231
236
 
237
+ // First-DM activation nudge (dormant users only): if signed in and this user
238
+ // has never sent a DM, point them at a real human who's active right now.
239
+ // firstDmNudge self-gates so people who already message never see it.
240
+ if (isInit) {
241
+ let threads = [];
242
+ try { threads = memory.listThreads(); } catch (e) {}
243
+ const activeOthers = active.filter(u => u.handle !== myHandle);
244
+ const nudge = firstDmNudge(activeOthers, threads);
245
+ if (nudge) display += nudge.replace(/^\n+/, '') + `\n\n`;
246
+ }
247
+
232
248
  // Away section (expanded with messages if present)
233
249
  if (away.length > 0) {
234
250
  display += `---\n\n`;
package/version.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
- "version": "0.5.10",
2
+ "version": "0.5.11",
3
3
  "updated": "2026-07-03",
4
- "changelog": "Play anything. `vibe_play @handle` is the open channel underneath the games it carries an arbitrary state object over your DM thread and lets you and the other person's Claude improvise the rules to whatever you invent: chess, 20 questions, a co-written story, battleship, a shared drawing, a debate. No built-in game logic the creativity is yours. `vibe_game`, `vibe_poem`, and `vibe_corpse` are still here as quick on-ramps. Ship together, play together.",
4
+ "changelog": "Never messaged anyone yet? `vibe start` and `vibe who` now nudge you toward a real human who's around right now a one-tap, ready-to-paste `vibe dm` with an opener warmed by what they just shipped. It shows only until you send your first DM, then it's gone for good. The whole point of the green dot is that someone's actually there; this closes the gap between seeing that and saying hi.",
5
5
  "features": [
6
+ "First-DM nudge — if you've never messaged anyone, vibe start/who surface the most-recently-active real human as a one-tap opener, warmed by their latest ship. Self-heals: disappears the moment you send your first DM",
6
7
  "vibe_play — the open primitive: pass any JSON state + a note to another viber, read it back to take your turn, and improvise any activity two sessions can dream up",
7
8
  "vibe_corpse — write an exquisite corpse: alternate lines blind to all but the last, then seal for the big reveal",
8
9
  "vibe_game — play tic-tac-toe with another viber, move by move, over your DM thread",