slashvibe-mcp 0.5.23 → 0.5.25

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 CHANGED
@@ -116,31 +116,20 @@ This enables visual collaboration (diagrams, mockups, debug screenshots) directl
116
116
 
117
117
  ---
118
118
 
119
- ## Deprecated: Pair/Guest/Call APIs
120
-
121
- The following tools and APIs are **deprecated** in favor of Matrix rooms. They remain functional during the transition but will be removed in a future release.
122
-
123
- ### Deprecated MCP Tools
124
-
125
- | Tool | Replacement |
126
- |------|-------------|
127
- | `vibe_pair` | Use Matrix rooms -- invite collaborators to a shared room |
128
- | `vibe_guest` | Use `vibe_matrix_send` / `vibe_matrix_read` |
129
- | `vibe_call` | Use Matrix rooms + Jitsi for voice (Jigasi bridge for transcription) |
130
-
131
- ### Deprecated APIs (7 endpoints)
132
-
133
- | API | Status |
134
- |-----|--------|
135
- | `/api/pair/request` | Deprecated -- use Matrix room invites |
136
- | `/api/pair/accept` | Deprecated -- use Matrix room invites |
137
- | `/api/call/signal` | Deprecated -- use Element/Jitsi |
138
- | `/api/call/status` | Deprecated -- use Element/Jitsi |
139
- | `/api/call/ice-servers` | Deprecated -- use Element/Jitsi |
140
- | `/api/session/guest` | Deprecated -- use Matrix messages |
141
- | `/api/session/live` | Deprecated -- use Matrix rooms |
142
-
143
- **Why the change:** Matrix rooms provide persistent history, support N participants (not just pairs), work across models (Claude + Codex + any), and federate with the broader Matrix ecosystem. The pair/guest/call system was limited to 2-player KV-backed sessions with short TTLs.
119
+ ## Pair/Guest/Call Transport
120
+
121
+ Pairing, live sessions, guest turns, and WebRTC signaling use the supported
122
+ REST/KV transport. Matrix rooms are an optional collaboration surface, not a
123
+ replacement or migration target for these APIs.
124
+
125
+ | Surface | Contract |
126
+ |---------|----------|
127
+ | `vibe_pair` | Authenticated request plus explicit acceptance |
128
+ | `vibe_guest` | Target opt-in plus accepted pair for human sessions |
129
+ | `vibe_call` | Transcript delivery through the consented guest-session path |
130
+ | `/api/pair`, `/api/pair/request`, `/api/pair/accept` | Supported pairing handshakes |
131
+ | `/api/session/live`, `/api/session/guest` | Supported live collaboration transport |
132
+ | `/api/call/*` | Supported WebRTC signaling and lifecycle transport |
144
133
 
145
134
  ---
146
135
 
package/api-auth.js ADDED
@@ -0,0 +1,11 @@
1
+ const config = require('./config');
2
+
3
+ function apiHeaders(extra = {}) {
4
+ const token = config.getAuthToken();
5
+ return {
6
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
7
+ ...extra,
8
+ };
9
+ }
10
+
11
+ module.exports = { apiHeaders };
@@ -17,6 +17,7 @@
17
17
 
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
+ const { apiHeaders } = require('../api-auth');
20
21
 
21
22
  const VIBE_DIR = path.join(process.env.HOME, '.vibe');
22
23
  const VIBECODINGS_DIR = path.join(process.env.HOME, '.vibecodings');
@@ -41,7 +42,10 @@ process.stdin.on('end', () => {});
41
42
 
42
43
  // Fetch guest messages (with ack to clear after reading)
43
44
  const url = `${API_BASE}/api/session/guest?handle=${encodeURIComponent(handle)}&ack=true`;
44
- const resp = await fetch(url, { signal: AbortSignal.timeout(3000) });
45
+ const resp = await fetch(url, {
46
+ headers: apiHeaders(),
47
+ signal: AbortSignal.timeout(3000),
48
+ });
45
49
  if (!resp.ok) process.exit(0);
46
50
 
47
51
  const data = await resp.json();
@@ -50,6 +50,23 @@ get_handle() {
50
50
  echo "$handle"
51
51
  }
52
52
 
53
+ get_token() {
54
+ if ! command -v jq &>/dev/null; then
55
+ return 1
56
+ fi
57
+ local token=""
58
+ if [ -f "$VIBE_DIR/config.json" ]; then
59
+ token=$(jq -r '.authToken // .privyToken // empty' "$VIBE_DIR/config.json" 2>/dev/null)
60
+ fi
61
+ if [ -z "$token" ] && [ -f "$VIBE_DIR/auth.json" ]; then
62
+ token=$(jq -r '.token // empty' "$VIBE_DIR/auth.json" 2>/dev/null)
63
+ fi
64
+ if [ -z "$token" ] && [ -f "$VIBECODINGS_DIR/config.json" ]; then
65
+ token=$(jq -r '.authToken // .privyToken // empty' "$VIBECODINGS_DIR/config.json" 2>/dev/null)
66
+ fi
67
+ echo "$token"
68
+ }
69
+
53
70
  # ─── Cooldown check ──────────────────────────────────────────────
54
71
  # Prevents hammering the API on rapid successive tool calls
55
72
  check_cooldown() {
@@ -84,6 +101,10 @@ HANDLE=$(get_handle)
84
101
  if [ -z "$HANDLE" ]; then
85
102
  exit 0 # Not authenticated, nothing to check
86
103
  fi
104
+ TOKEN=$(get_token || true)
105
+ if [ -z "$TOKEN" ]; then
106
+ exit 0
107
+ fi
87
108
 
88
109
  # Cooldown: skip if we checked recently
89
110
  if ! check_cooldown; then
@@ -95,6 +116,7 @@ update_cooldown
95
116
 
96
117
  # Fetch guest messages (with ack=true to clear after reading)
97
118
  RESPONSE=$(curl -s --max-time 3 \
119
+ -H "Authorization: Bearer $TOKEN" \
98
120
  "${API_BASE}/api/session/guest?handle=$(printf '%s' "$HANDLE" | jq -sRr @uri 2>/dev/null || echo "$HANDLE")&ack=true" \
99
121
  2>/dev/null || echo '{"success":false}')
100
122
 
package/index.js CHANGED
@@ -16,6 +16,7 @@ const store = require('./store');
16
16
  const prompts = require('./prompts');
17
17
  const NotificationEmitter = require('./notification-emitter');
18
18
  const authStore = require('./auth-store');
19
+ const { apiHeaders } = require('./api-auth');
19
20
 
20
21
  // Tools that shouldn't show presence footer (would be redundant/noisy)
21
22
  const SKIP_FOOTER_TOOLS = ['vibe_init', 'vibe_doctor', 'vibe_test', 'vibe_update', 'vibe_settings', 'vibe_notifications'];
@@ -116,7 +117,9 @@ async function fetchGuestMessages(handle) {
116
117
  const apiUrl = config.getApiUrl?.() || 'https://www.slashvibe.dev';
117
118
  try {
118
119
  // Fetch with ack=true to clear after reading (prevents re-injection)
119
- const resp = await fetch(`${apiUrl}/api/session/guest?handle=${encodeURIComponent(handle)}&ack=true`);
120
+ const resp = await fetch(`${apiUrl}/api/session/guest?handle=${encodeURIComponent(handle)}&ack=true`, {
121
+ headers: apiHeaders(),
122
+ });
120
123
  if (!resp.ok) return [];
121
124
  const data = await resp.json();
122
125
  if (data.success && Array.isArray(data.messages) && data.messages.length > 0) {
@@ -626,4 +629,4 @@ class VibeMCPServer {
626
629
 
627
630
  // Start
628
631
  const server = new VibeMCPServer();
629
- server.start();
632
+ server.start();
package/notify.js CHANGED
@@ -15,6 +15,7 @@ const os = require('os');
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
17
  const config = require('./config');
18
+ const { apiHeaders } = require('./api-auth');
18
19
 
19
20
  // Track what we've already notified about
20
21
  const NOTIFY_STATE_FILE = path.join(config.VIBE_DIR, '.notify_state.json');
@@ -397,7 +398,9 @@ async function checkAll(store) {
397
398
  // Check for guest session messages (multiplayer)
398
399
  try {
399
400
  const apiUrl = config.getApiUrl();
400
- const guestResp = await fetch(`${apiUrl}/api/session/guest?handle=${encodeURIComponent(myHandle)}`);
401
+ const guestResp = await fetch(`${apiUrl}/api/session/guest?handle=${encodeURIComponent(myHandle)}`, {
402
+ headers: apiHeaders(),
403
+ });
401
404
  const guestData = await guestResp.json();
402
405
  if (guestData.success && guestData.messages && guestData.messages.length > 0) {
403
406
  checkGuestMessages(guestData.messages);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "slashvibe-mcp",
3
- "version": "0.5.23",
3
+ "version": "0.5.25",
4
4
  "mcpName": "io.github.vibecodinginc/vibe",
5
- "description": "Social layer for Claude Code - DMs, presence, Matrix multiplayer rooms, and connection between AI-assisted developers",
5
+ "description": "Social layer for Claude Code - identity, presence, messaging, and consented live collaboration",
6
6
  "main": "index.js",
7
7
  "bin": {
8
8
  "slashvibe-mcp": "./cli.js",
@@ -46,6 +46,7 @@
46
46
  "index.js",
47
47
  "setup.js",
48
48
  "auth-store.js",
49
+ "api-auth.js",
49
50
  "auto-update.js",
50
51
  "config.js",
51
52
  "crypto.js",
@@ -73,4 +74,4 @@
73
74
  "crossword-layout-generator": "^0.1.1",
74
75
  "node-fetch": "^2.7.0"
75
76
  }
76
- }
77
+ }
package/presence.js CHANGED
@@ -15,6 +15,7 @@
15
15
  const config = require('./config');
16
16
  const store = require('./store');
17
17
  const notify = require('./notify');
18
+ const { apiHeaders } = require('./api-auth');
18
19
 
19
20
  let heartbeatInterval = null;
20
21
  let guestPollInterval = null;
@@ -99,7 +100,9 @@ async function pollGuestMessages() {
99
100
  if (!handle) return;
100
101
 
101
102
  const apiUrl = config.getApiUrl?.() || 'https://www.slashvibe.dev';
102
- const resp = await fetch(`${apiUrl}/api/session/guest?handle=${encodeURIComponent(handle)}`);
103
+ const resp = await fetch(`${apiUrl}/api/session/guest?handle=${encodeURIComponent(handle)}`, {
104
+ headers: apiHeaders(),
105
+ });
103
106
  if (!resp.ok) return;
104
107
 
105
108
  const data = await resp.json();
package/store/api.js CHANGED
@@ -1214,7 +1214,17 @@ async function sendArtifactCard(to, card) {
1214
1214
  }
1215
1215
  }
1216
1216
 
1217
+ // The "slower" primitive — set your one notification pace (normal|slower|quiet).
1218
+ async function setNotificationPace(pace) {
1219
+ try {
1220
+ return await request('POST', '/api/settings/notifications', { action: 'set_pace', pace });
1221
+ } catch (e) {
1222
+ return { success: false, error: e.message };
1223
+ }
1224
+ }
1225
+
1217
1226
  module.exports = {
1227
+ setNotificationPace,
1218
1228
  // Session
1219
1229
  registerSession,
1220
1230
  setSessionId,
package/tools/call.js CHANGED
@@ -1,11 +1,6 @@
1
1
  /**
2
2
  * vibe call — Inject call transcripts into paired sessions
3
3
  *
4
- * DEPRECATED: Use Matrix rooms with Jitsi for voice calls. The Jigasi bridge
5
- * can pipe call transcription (via Whisper/Deepgram) as an ambient API endpoint.
6
- * Matrix rooms provide persistent transcript history for all participants.
7
- *
8
- *
9
4
  * When on a call with your pair partner, paste or pipe in the
10
5
  * transcript and it gets sent to both Claude Code sessions.
11
6
  *
@@ -15,12 +10,13 @@
15
10
  */
16
11
 
17
12
  const config = require('../config');
13
+ const { apiHeaders } = require('../api-auth');
18
14
 
19
15
  const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
20
16
 
21
17
  const definition = {
22
18
  name: 'vibe_call',
23
- description: '[DEPRECATED — use Matrix rooms + Jitsi] Inject call transcripts into your paired partner\'s Claude Code session.',
19
+ description: 'Inject call transcripts into an accepted pair partner\'s Claude Code session.',
24
20
  inputSchema: {
25
21
  type: 'object',
26
22
  properties: {
@@ -67,7 +63,9 @@ async function handler(args) {
67
63
  let partner = overridePartner;
68
64
  if (!partner) {
69
65
  try {
70
- const resp = await fetch(`${API_URL}/api/pair?handle=${encodeURIComponent(handle)}`);
66
+ const resp = await fetch(`${API_URL}/api/pair?handle=${encodeURIComponent(handle)}`, {
67
+ headers: apiHeaders(),
68
+ });
71
69
  const data = await resp.json();
72
70
  if (data.paired) {
73
71
  partner = data.partner;
@@ -92,7 +90,9 @@ async function handler(args) {
92
90
 
93
91
  async function callStatus(handle) {
94
92
  try {
95
- const resp = await fetch(`${API_URL}/api/pair?handle=${encodeURIComponent(handle)}`);
93
+ const resp = await fetch(`${API_URL}/api/pair?handle=${encodeURIComponent(handle)}`, {
94
+ headers: apiHeaders(),
95
+ });
96
96
  const data = await resp.json();
97
97
 
98
98
  if (!data.paired) {
@@ -127,7 +127,7 @@ async function injectTranscript(from, to, transcript) {
127
127
  try {
128
128
  const resp = await fetch(`${API_URL}/api/session/guest`, {
129
129
  method: 'POST',
130
- headers: { 'Content-Type': 'application/json' },
130
+ headers: apiHeaders({ 'Content-Type': 'application/json' }),
131
131
  body: JSON.stringify({ from, to, message: chunk }),
132
132
  });
133
133
  const data = await resp.json();
@@ -1,11 +1,6 @@
1
1
  /**
2
2
  * vibe guest — Multiplayer session messaging
3
3
  *
4
- * DEPRECATED: Use Matrix rooms instead. The Matrix bot + PostToolUse hook
5
- * provides the same ambient message injection with persistent history and
6
- * support for N participants. See vibe_matrix_read, vibe_matrix_send.
7
- *
8
- *
9
4
  * Check for guest messages from people watching your session,
10
5
  * or send a message into someone else's Claude Code session.
11
6
  *
@@ -14,12 +9,13 @@
14
9
  */
15
10
 
16
11
  const config = require('../config');
12
+ const { apiHeaders } = require('../api-auth');
17
13
 
18
14
  const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
19
15
 
20
16
  const definition = {
21
17
  name: 'vibe_guest',
22
- description: '[DEPRECATED — use Matrix rooms] Check for guest messages in your session, or send a message into someone else\'s Claude Code session.',
18
+ description: 'Check for consented guest messages in your session, or send a message into an accepted partner\'s Claude Code session.',
23
19
  inputSchema: {
24
20
  type: 'object',
25
21
  properties: {
@@ -69,7 +65,7 @@ async function handler(args) {
69
65
  async function checkMessages(handle, ack = false) {
70
66
  try {
71
67
  const url = `${API_URL}/api/session/guest?handle=${encodeURIComponent(handle)}${ack ? '&ack=true' : ''}`;
72
- const resp = await fetch(url);
68
+ const resp = await fetch(url, { headers: apiHeaders() });
73
69
  const data = await resp.json();
74
70
 
75
71
  if (!data.success || !data.messages || data.messages.length === 0) {
@@ -100,7 +96,7 @@ async function sendMessage(from, to, message) {
100
96
  try {
101
97
  const resp = await fetch(`${API_URL}/api/session/guest`, {
102
98
  method: 'POST',
103
- headers: { 'Content-Type': 'application/json' },
99
+ headers: apiHeaders({ 'Content-Type': 'application/json' }),
104
100
  body: JSON.stringify({ from, to, message }),
105
101
  });
106
102
  const data = await resp.json();
package/tools/inbox.js CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  const config = require('../config');
6
+ const { apiHeaders } = require('../api-auth');
6
7
  const store = require('../store');
7
8
  const notify = require('../notify');
8
9
  const patterns = require('../intelligence/patterns');
@@ -139,7 +140,9 @@ async function handler(args) {
139
140
  let guestMessages = [];
140
141
  try {
141
142
  const apiUrl = config.getApiUrl ? config.getApiUrl() : 'https://www.slashvibe.dev';
142
- const guestResp = await fetch(`${apiUrl}/api/session/guest?handle=${encodeURIComponent(myHandle)}`);
143
+ const guestResp = await fetch(`${apiUrl}/api/session/guest?handle=${encodeURIComponent(myHandle)}`, {
144
+ headers: apiHeaders(),
145
+ });
143
146
  const guestData = await guestResp.json();
144
147
  if (guestData.success && guestData.messages && guestData.messages.length > 0) {
145
148
  guestMessages = guestData.messages;
package/tools/pair.js CHANGED
@@ -1,11 +1,6 @@
1
1
  /**
2
2
  * vibe pair — Link up with another user for collaborative coding
3
3
  *
4
- * DEPRECATED: Use Matrix rooms instead. Matrix rooms support N participants,
5
- * persistent history, and cross-model collaboration (Claude + Codex + any).
6
- * See vibe_matrix_rooms, vibe_matrix_read, vibe_matrix_send.
7
- *
8
- *
9
4
  * When paired:
10
5
  * - Your presence shows "paired with @partner"
11
6
  * - You can send guest messages into each other's sessions
@@ -13,19 +8,20 @@
13
8
  */
14
9
 
15
10
  const config = require('../config');
11
+ const { apiHeaders } = require('../api-auth');
16
12
 
17
13
  const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
18
14
 
19
15
  const definition = {
20
16
  name: 'vibe_pair',
21
- description: '[DEPRECATED — use Matrix rooms] Pair up with another /vibe user for collaborative coding. Start a pair session, check status, or disconnect.',
17
+ description: 'Pair with another /vibe user for collaborative coding. Pairing requires the other actor to accept.',
22
18
  inputSchema: {
23
19
  type: 'object',
24
20
  properties: {
25
21
  action: {
26
22
  type: 'string',
27
- description: 'Action: "start" (pair with someone), "stop" (end pair), "status" (check current pair)',
28
- enum: ['start', 'stop', 'status'],
23
+ description: 'Action: "start", "accept", "decline", "stop", or "status"',
24
+ enum: ['start', 'accept', 'decline', 'stop', 'status'],
29
25
  },
30
26
  partner: {
31
27
  type: 'string',
@@ -61,6 +57,11 @@ async function handler(args) {
61
57
  return await startPair(handle, partner, mode || 'coding');
62
58
  }
63
59
 
60
+ if (action === 'accept' || action === 'decline') {
61
+ if (!partner) return { content: [{ type: 'text', text: `Missing partner for ${action}.` }] };
62
+ return await respondToPair(handle, partner, action);
63
+ }
64
+
64
65
  if (action === 'stop') {
65
66
  return await stopPair(handle);
66
67
  }
@@ -70,10 +71,22 @@ async function handler(args) {
70
71
 
71
72
  async function checkStatus(handle) {
72
73
  try {
73
- const resp = await fetch(`${API_URL}/api/pair?handle=${encodeURIComponent(handle)}`);
74
+ const resp = await fetch(`${API_URL}/api/pair?handle=${encodeURIComponent(handle)}`, {
75
+ headers: apiHeaders(),
76
+ });
74
77
  const data = await resp.json();
75
78
 
76
79
  if (!data.paired) {
80
+ const incoming = data.incomingRequests || [];
81
+ if (incoming.length > 0) {
82
+ const request = incoming[0];
83
+ return {
84
+ content: [{
85
+ type: 'text',
86
+ text: `@${request.from} wants to pair (${request.mode || 'coding'}). Use action "accept" or "decline" with partner "${request.from}".`,
87
+ }],
88
+ };
89
+ }
77
90
  return { content: [{ type: 'text', text: 'Not paired with anyone. Use action "start" with a partner handle.' }] };
78
91
  }
79
92
 
@@ -89,11 +102,33 @@ async function checkStatus(handle) {
89
102
  }
90
103
  }
91
104
 
105
+ async function respondToPair(handle, partner, action) {
106
+ try {
107
+ const resp = await fetch(`${API_URL}/api/pair`, {
108
+ method: 'POST',
109
+ headers: apiHeaders({ 'Content-Type': 'application/json' }),
110
+ body: JSON.stringify({ handle, partner, action }),
111
+ });
112
+ const data = await resp.json();
113
+ if (!resp.ok || data.success === false) {
114
+ return { content: [{ type: 'text', text: `Could not ${action} pair request: ${data.message || data.error}` }] };
115
+ }
116
+ return {
117
+ content: [{
118
+ type: 'text',
119
+ text: action === 'accept' ? `Paired with @${partner}.` : `Declined @${partner}'s pair request.`,
120
+ }],
121
+ };
122
+ } catch (err) {
123
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }] };
124
+ }
125
+ }
126
+
92
127
  async function startPair(handle, partner, mode) {
93
128
  try {
94
129
  const resp = await fetch(`${API_URL}/api/pair`, {
95
130
  method: 'POST',
96
- headers: { 'Content-Type': 'application/json' },
131
+ headers: apiHeaders({ 'Content-Type': 'application/json' }),
97
132
  body: JSON.stringify({ handle, partner, action: 'start', mode }),
98
133
  });
99
134
  const data = await resp.json();
@@ -107,6 +142,15 @@ async function startPair(handle, partner, mode) {
107
142
  };
108
143
  }
109
144
 
145
+ if (data.pending) {
146
+ return {
147
+ content: [{
148
+ type: 'text',
149
+ text: `Pair request sent to @${partner}. Session sharing starts only after they accept.`,
150
+ }],
151
+ };
152
+ }
153
+
110
154
  return { content: [{ type: 'text', text: `Failed to pair: ${JSON.stringify(data)}` }] };
111
155
  } catch (err) {
112
156
  return { content: [{ type: 'text', text: `Error: ${err.message}` }] };
@@ -117,7 +161,7 @@ async function stopPair(handle) {
117
161
  try {
118
162
  await fetch(`${API_URL}/api/pair`, {
119
163
  method: 'POST',
120
- headers: { 'Content-Type': 'application/json' },
164
+ headers: apiHeaders({ 'Content-Type': 'application/json' }),
121
165
  body: JSON.stringify({ handle, action: 'stop' }),
122
166
  });
123
167
  return { content: [{ type: 'text', text: 'Pair ended.' }] };
package/tools/start.js CHANGED
@@ -12,6 +12,7 @@ const fs = require('fs');
12
12
  const path = require('path');
13
13
  const { execSync } = require('child_process');
14
14
  const config = require('../config');
15
+ const { apiHeaders } = require('../api-auth');
15
16
  const store = require('../store');
16
17
  const memory = require('../memory');
17
18
  const notify = require('../notify');
@@ -274,9 +275,10 @@ async function handler(args) {
274
275
  let pairStatus = null;
275
276
  try {
276
277
  const apiUrl = config.getApiUrl();
278
+ const headers = apiHeaders();
277
279
  const [guestResp, pairResp] = await Promise.all([
278
- fetch(`${apiUrl}/api/session/guest?handle=${encodeURIComponent(myHandle)}`),
279
- fetch(`${apiUrl}/api/pair?handle=${encodeURIComponent(myHandle)}`),
280
+ fetch(`${apiUrl}/api/session/guest?handle=${encodeURIComponent(myHandle)}`, { headers }),
281
+ fetch(`${apiUrl}/api/pair?handle=${encodeURIComponent(myHandle)}`, { headers }),
280
282
  ]);
281
283
  const guestData = await guestResp.json();
282
284
  if (guestData.success && guestData.messages && guestData.messages.length > 0) {
package/tools/status.js CHANGED
@@ -29,13 +29,13 @@ const SPECIAL_MODES = ['guided', 'freeform'];
29
29
 
30
30
  const definition = {
31
31
  name: 'vibe_status',
32
- description: 'Set your mood/status. Options: shipping, thinking, afk, debugging, pairing, deep, celebrating, struggling, away, back, clear',
32
+ description: 'Set your mood/status, or your notification pace. Mood: shipping, thinking, afk, debugging, pairing, deep, celebrating, struggling, away, back, clear. Pace: slower (only ping when away), quiet (no pushes), normal.',
33
33
  inputSchema: {
34
34
  type: 'object',
35
35
  properties: {
36
36
  mood: {
37
37
  type: 'string',
38
- description: 'Your mood (shipping, thinking, afk, debugging, pairing, deep, celebrating, struggling, away, back, clear)'
38
+ description: 'A mood (shipping, thinking, afk, debugging, pairing, deep, celebrating, struggling, away, back, clear) OR a notification pace (slower, quiet, normal)'
39
39
  },
40
40
  message: {
41
41
  type: 'string',
@@ -57,6 +57,25 @@ async function handler(args) {
57
57
  const moodKey = mood.toLowerCase().replace(/[^a-z]/g, '');
58
58
  const handle = config.getHandle();
59
59
 
60
+ // The "slower" primitive (ratified in the Living Room): one notification pace
61
+ // across every surface, not a per-channel mute. `slower` = only ping me when
62
+ // I've stepped away; `quiet` = hold all pushes (badge still updates); `normal`
63
+ // = default. A first-class human command.
64
+ const PACE = { slower: 'slower', quiet: 'quiet', normal: 'normal', faster: 'normal' };
65
+ if (PACE[moodKey]) {
66
+ const pace = PACE[moodKey];
67
+ const result = await store.setNotificationPace(pace);
68
+ if (result?.success === false) {
69
+ return { display: `Couldn't set pace: ${result.error || 'unknown error'}` };
70
+ }
71
+ const blurb = {
72
+ slower: '🐢 **slower** — you\'ll only get pushed when you\'ve stepped away. The board still shows everything, quietly.',
73
+ quiet: '🤫 **quiet** — no pushes; unread still counts silently. Say `vibe status normal` to turn them back on.',
74
+ normal: '🔔 **normal** — notifications back to default.',
75
+ }[pace];
76
+ return { display: blurb };
77
+ }
78
+
60
79
  // Handle special modes (guided/freeform)
61
80
  if (moodKey === 'guided') {
62
81
  config.setGuidedMode(true);
package/version.json CHANGED
@@ -1,17 +1,15 @@
1
1
  {
2
- "version": "0.5.19",
3
- "updated": "2026-07-08",
4
- "changelog": "Your terminal is now a first-class citizen of the conversation. DMs sent from any surface \u2014 the iPhone app, Vibe Buddy, the web \u2014 now land inside your live Claude Code session within seconds, surfaced once with the sender and a preview. And reading a message in the terminal finally counts: vibe inbox / open / reply clear the unread badge on your phone and desktop too, not just here.",
2
+ "version": "0.5.25",
3
+ "updated": "2026-07-10",
4
+ "changelog": "Live collaboration now has a real actor boundary. Pairing requires acceptance, private session reads use the signed-in actor instead of a viewer string, and messages enter a human session only after the owner opts in. All MCP pair and guest calls now carry the OAuth token.",
5
5
  "features": [
6
- "DMs reach your live session \u2014 a message from ANY surface (iOS, Buddy, web, another terminal) is injected into your session's ambient footer within one 15s poll cycle, once per message, with clear external-input guardrails. Previously only Buddy's guest channel could reach a session; ordinary DMs sat behind an unread counter",
7
- "Terminal reads sync everywhere \u2014 vibe_inbox / vibe_open / vibe_reply now advance the shared per-handle read cursor (they were silently no-oping), so reading in Claude Code clears the badge on iOS and Buddy",
8
- "Injected DMs never double-fire \u2014 each message body enters context exactly once; read state only advances when you actually open or reply to the thread, so badges stay honest about what you've engaged with"
6
+ "Pair requests are two-party: the target accepts or declines before session context is shared",
7
+ "Paired and private session reads derive the viewer from a signed token; query-string handle spoofing no longer grants access",
8
+ "Guest messages and handle/claude DMs require target opt-in plus an accepted pair for human sessions",
9
+ "Public house-agent sessions retain their explicit open-door policy",
10
+ "The MCP version manifest, package, and lockfile now report the same release"
9
11
  ],
10
- "deprecated": [
11
- "vibe_pair \u2014 use Matrix room invites",
12
- "vibe_guest \u2014 use vibe_matrix_send / vibe_matrix_read",
13
- "vibe_call \u2014 use Matrix rooms + Jitsi"
14
- ],
15
- "breaking": false,
12
+ "deprecated": [],
13
+ "breaking": true,
16
14
  "updateUrl": "https://www.slashvibe.dev/api/version"
17
- }
15
+ }