slashvibe-mcp 0.5.10 → 0.5.12

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.12",
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/init.js CHANGED
@@ -263,9 +263,13 @@ async function sendWelcomeMessage(handle, one_liner) {
263
263
  }
264
264
  }
265
265
 
266
+ // The exact link a user can click if the browser didn't pop open on its own.
267
+ // Deterministic (fixed callback port), so it's safe to show verbatim anywhere.
268
+ const MANUAL_LOGIN_URL = `https://www.slashvibe.dev/login?redirect=${encodeURIComponent(`http://localhost:${CALLBACK_PORT}/callback`)}`;
269
+
266
270
  const definition = {
267
271
  name: 'vibe_init',
268
- description: 'Join /vibe social network. Opens browser for GitHub login - NO INPUT NEEDED. Your GitHub username becomes your handle automatically. Just run this and complete the browser auth.',
272
+ description: `Join /vibe social network. Opens the browser for GitHub sign-in NO INPUT NEEDED; the user's GitHub username becomes their handle automatically. This BLOCKS for up to 2 minutes waiting for the browser login to finish, so BEFORE it returns the user sees only a spinner. IMPORTANT: right when you call this, tell the user in your own words that their browser is opening to sign in with GitHub, and that if the tab didn't pop open they can click this link instead: ${MANUAL_LOGIN_URL} — then finish the login there and come back.`,
269
273
  inputSchema: {
270
274
  type: 'object',
271
275
  properties: {
@@ -810,44 +814,47 @@ To check messages: \`vibe inbox\`${emailNudge}`
810
814
  } catch (err) {
811
815
  if (err.message === 'AUTH_IN_PROGRESS') {
812
816
  return {
813
- display: `## Auth already in progress
817
+ display: `## A login is already running
814
818
 
815
- Another login is running. Either:
816
- 1. **Complete it** in your browser, or
817
- 2. **Wait 30 seconds** and try again
819
+ You've got a sign-in waiting in your browser. Either:
820
+ 1. **Finish it** in the browser tab that opened, or
821
+ 2. **Open it yourself** if the tab didn't pop up:
822
+ ${MANUAL_LOGIN_URL}
818
823
 
819
- If stuck, kill the auth server:
824
+ Still stuck? Clear it and start over:
820
825
  \`\`\`
821
826
  lsof -ti:9876 | xargs kill
822
827
  \`\`\`
823
- Then run \`vibe init\` again.`
828
+ Then say **"let's vibe"** again.`
824
829
  };
825
830
  }
826
831
 
827
832
  if (err.message === 'AUTH_TIMEOUT') {
828
833
  return {
829
- display: `## Auth timed out
834
+ display: `## The sign-in timed out
830
835
 
831
- The browser login wasn't completed within 2 minutes.
836
+ The browser login wasn't finished within 2 minutes — no problem, just start it again.
832
837
 
833
- **Try again:**
834
- Just say "add the vibe mcp server" and complete the browser auth faster this time.
838
+ **1. Say "let's vibe"** to reopen the login.
839
+ **2. If the browser tab doesn't pop up,** click this link yourself:
840
+ ${MANUAL_LOGIN_URL}
841
+ **3. Sign in with GitHub** in that tab, then come back here.
835
842
 
836
- **Tip:** Keep Claude Code visible so you can see when auth completes.`
843
+ _Tip: keep this window and the browser both visible so you can see when it finishes._`
837
844
  };
838
845
  }
839
846
 
840
847
  return {
841
- display: `## Failed to authenticate
848
+ display: `## Couldn't finish sign-in
842
849
 
843
- **Error:** ${err.message}
850
+ **What happened:** ${err.message}
844
851
 
845
- **Quick fixes:**
846
- 1. Check your internet connection
847
- 2. Try again: just say "vibe init"
848
- 3. Check status: slashvibe.dev/status
852
+ **Try this:**
853
+ 1. **Click the login link directly:** ${MANUAL_LOGIN_URL}
854
+ 2. Finish the GitHub sign-in in that tab, then come back
855
+ 3. Or say **"let's vibe"** to try the whole thing again
849
856
 
850
- **Need help?** DM @seth on Twitter or email seth@slashvibe.dev`
857
+ **Still stuck?** Email seth@slashvibe.dev happy to get you in.`
851
858
  };
852
859
  }
853
860
  }
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,10 @@
1
1
  {
2
- "version": "0.5.10",
2
+ "version": "0.5.12",
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": "First-run just got un-losable. Saying `let's vibe` opens your browser for GitHub sign-inand now the tool tells you that's happening, hands you a clickable login link if the tab didn't pop, and if anything times out it says exactly what to do (`let's vibe` again) instead of an unrelated hint. No more staring at a silent prompt wondering if it worked.",
5
5
  "features": [
6
+ "Bulletproof first-run — `let's vibe` now narrates the browser sign-in, gives you a clickable fallback login link, and every timeout/error path tells you the correct retry. You can't get stranded",
7
+ "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
8
  "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
9
  "vibe_corpse — write an exquisite corpse: alternate lines blind to all but the last, then seal for the big reveal",
8
10
  "vibe_game — play tic-tac-toe with another viber, move by move, over your DM thread",