slashvibe-mcp 0.2.8 → 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.
Files changed (161) hide show
  1. package/README.md +41 -58
  2. package/analytics.js +107 -0
  3. package/auth-store.js +148 -0
  4. package/auto-update.js +130 -0
  5. package/bridges/bridge-monitor.js +388 -0
  6. package/bridges/discord-bot.js +431 -0
  7. package/bridges/farcaster.js +299 -0
  8. package/bridges/telegram.js +261 -0
  9. package/bridges/webhook-health.js +420 -0
  10. package/bridges/webhook-server.js +437 -0
  11. package/bridges/whatsapp.js +441 -0
  12. package/bridges/x-webhook.js +423 -0
  13. package/config.js +27 -15
  14. package/games/arcade.js +406 -0
  15. package/games/chess.js +451 -0
  16. package/games/colorguess.js +343 -0
  17. package/games/crossword-words.js +171 -0
  18. package/games/crossword.js +461 -0
  19. package/games/drawing.js +347 -0
  20. package/games/gameroulette.js +300 -0
  21. package/games/gamerouter.js +336 -0
  22. package/games/gamestatus.js +337 -0
  23. package/games/guessnumber.js +209 -0
  24. package/games/hangman.js +279 -0
  25. package/games/memory.js +338 -0
  26. package/games/multiplayer-tictactoe.js +389 -0
  27. package/games/pixelart.js +399 -0
  28. package/games/quickduel.js +354 -0
  29. package/games/riddle.js +371 -0
  30. package/games/rockpaperscissors.js +291 -0
  31. package/games/snake.js +406 -0
  32. package/games/storybuilder.js +343 -0
  33. package/games/tictactoe.js +345 -0
  34. package/games/twentyquestions.js +286 -0
  35. package/games/twotruths.js +207 -0
  36. package/games/werewolf.js +508 -0
  37. package/games/wordassociation.js +247 -0
  38. package/games/wordchain.js +135 -0
  39. package/index.js +77 -53
  40. package/intelligence/index.js +9 -2
  41. package/intelligence/interests.js +369 -0
  42. package/notification-emitter.js +77 -0
  43. package/notify.js +5 -1
  44. package/package.json +18 -6
  45. package/prompts.js +1 -1
  46. package/protocol/index.js +73 -0
  47. package/setup.js +402 -0
  48. package/store/api.js +436 -211
  49. package/store/profiles.js +160 -12
  50. package/tools/_actions.js +362 -21
  51. package/tools/_discovery.js +119 -26
  52. package/tools/_shared/index.js +64 -0
  53. package/tools/_shared.js +234 -0
  54. package/tools/_work-context.js +338 -0
  55. package/tools/_work-context.manual-test.js +199 -0
  56. package/tools/_work-context.test.js +260 -0
  57. package/tools/activity.js +220 -0
  58. package/tools/analytics.js +191 -0
  59. package/tools/approve.js +197 -0
  60. package/tools/artifact-create.js +14 -3
  61. package/tools/artifacts-price.js +107 -0
  62. package/tools/available.js +120 -0
  63. package/tools/broadcast.js +286 -0
  64. package/tools/chat.js +202 -0
  65. package/tools/collaborative-drawing.js +1 -1
  66. package/tools/connection-status.js +178 -0
  67. package/tools/discover.js +350 -34
  68. package/tools/dm.js +80 -8
  69. package/tools/earnings.js +126 -0
  70. package/tools/feed.js +35 -4
  71. package/tools/follow.js +224 -0
  72. package/tools/friends.js +207 -0
  73. package/tools/gig-browse.js +206 -0
  74. package/tools/gig-complete.js +144 -0
  75. package/tools/help.js +3 -3
  76. package/tools/idea.js +9 -2
  77. package/tools/inbox.js +289 -105
  78. package/tools/init.js +106 -27
  79. package/tools/invite.js +15 -4
  80. package/tools/migrate.js +3 -3
  81. package/tools/multiplayer-game.js +1 -1
  82. package/tools/onboarding.js +7 -7
  83. package/tools/open.js +143 -12
  84. package/tools/party-game.js +1 -1
  85. package/tools/plan.js +225 -0
  86. package/tools/proof-of-work.js +144 -0
  87. package/tools/reply.js +166 -0
  88. package/tools/report.js +1 -1
  89. package/tools/request.js +17 -3
  90. package/tools/schedule.js +367 -0
  91. package/tools/search-messages.js +123 -0
  92. package/tools/session.js +420 -0
  93. package/tools/session_price.js +128 -0
  94. package/tools/settings.js +90 -2
  95. package/tools/ship.js +30 -7
  96. package/tools/smart-check.js +201 -0
  97. package/tools/start.js +147 -12
  98. package/tools/status.js +53 -6
  99. package/tools/stuck.js +297 -0
  100. package/tools/subscribe.js +148 -0
  101. package/tools/subscriptions.js +134 -0
  102. package/tools/suggest-tags.js +6 -8
  103. package/tools/tag-suggestions.js +1 -1
  104. package/tools/tip.js +150 -77
  105. package/tools/token.js +4 -4
  106. package/tools/update.js +1 -1
  107. package/tools/wallet.js +221 -79
  108. package/tools/watch.js +157 -0
  109. package/tools/who.js +30 -1
  110. package/tools/withdraw.js +145 -0
  111. package/tools/work-summary.js +96 -0
  112. package/version.json +10 -8
  113. package/LICENSE +0 -21
  114. package/store/sqlite.js +0 -347
  115. /package/tools/{auto-suggest-connections.js → _deprecated/auto-suggest-connections.js} +0 -0
  116. /package/tools/{away.js → _deprecated/away.js} +0 -0
  117. /package/tools/{back.js → _deprecated/back.js} +0 -0
  118. /package/tools/{bootstrap-skills.js → _deprecated/bootstrap-skills.js} +0 -0
  119. /package/tools/{bridge-dashboard.js → _deprecated/bridge-dashboard.js} +0 -0
  120. /package/tools/{bridge-health.js → _deprecated/bridge-health.js} +0 -0
  121. /package/tools/{bridge-live.js → _deprecated/bridge-live.js} +0 -0
  122. /package/tools/{bridges.js → _deprecated/bridges.js} +0 -0
  123. /package/tools/{colorguess.js → _deprecated/colorguess.js} +0 -0
  124. /package/tools/{discover-insights.js → _deprecated/discover-insights.js} +0 -0
  125. /package/tools/{discover-momentum.js → _deprecated/discover-momentum.js} +0 -0
  126. /package/tools/{discovery-analytics.js → _deprecated/discovery-analytics.js} +0 -0
  127. /package/tools/{discovery-auto-suggest.js → _deprecated/discovery-auto-suggest.js} +0 -0
  128. /package/tools/{discovery-bootstrap.js → _deprecated/discovery-bootstrap.js} +0 -0
  129. /package/tools/{discovery-daily.js → _deprecated/discovery-daily.js} +0 -0
  130. /package/tools/{discovery-dashboard.js → _deprecated/discovery-dashboard.js} +0 -0
  131. /package/tools/{discovery-digest.js → _deprecated/discovery-digest.js} +0 -0
  132. /package/tools/{discovery-hub.js → _deprecated/discovery-hub.js} +0 -0
  133. /package/tools/{discovery-insights.js → _deprecated/discovery-insights.js} +0 -0
  134. /package/tools/{discovery-momentum.js → _deprecated/discovery-momentum.js} +0 -0
  135. /package/tools/{discovery-monitor.js → _deprecated/discovery-monitor.js} +0 -0
  136. /package/tools/{discovery-proactive.js → _deprecated/discovery-proactive.js} +0 -0
  137. /package/tools/{draw.js → _deprecated/draw.js} +0 -0
  138. /package/tools/{farcaster.js → _deprecated/farcaster.js} +0 -0
  139. /package/tools/{forget.js → _deprecated/forget.js} +0 -0
  140. /package/tools/{games-catalog.js → _deprecated/games-catalog.js} +0 -0
  141. /package/tools/{games.js → _deprecated/games.js} +0 -0
  142. /package/tools/{guessnumber.js → _deprecated/guessnumber.js} +0 -0
  143. /package/tools/{hangman.js → _deprecated/hangman.js} +0 -0
  144. /package/tools/{multiplayer-tictactoe.js → _deprecated/multiplayer-tictactoe.js} +0 -0
  145. /package/tools/{mute.js → _deprecated/mute.js} +0 -0
  146. /package/tools/{recall.js → _deprecated/recall.js} +0 -0
  147. /package/tools/{remember.js → _deprecated/remember.js} +0 -0
  148. /package/tools/{riddle.js → _deprecated/riddle.js} +0 -0
  149. /package/tools/{run-bootstrap.js → _deprecated/run-bootstrap.js} +0 -0
  150. /package/tools/{skills-analytics.js → _deprecated/skills-analytics.js} +0 -0
  151. /package/tools/{skills-bootstrap.js → _deprecated/skills-bootstrap.js} +0 -0
  152. /package/tools/{skills-dashboard.js → _deprecated/skills-dashboard.js} +0 -0
  153. /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
  154. /package/tools/{skills.js → _deprecated/skills.js} +0 -0
  155. /package/tools/{smart-intro.js → _deprecated/smart-intro.js} +0 -0
  156. /package/tools/{storybuilder.js → _deprecated/storybuilder.js} +0 -0
  157. /package/tools/{telegram-bot.js → _deprecated/telegram-bot.js} +0 -0
  158. /package/tools/{telegram-setup.js → _deprecated/telegram-setup.js} +0 -0
  159. /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
  160. /package/tools/{twentyquestions.js → _deprecated/twentyquestions.js} +0 -0
  161. /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
@@ -0,0 +1,420 @@
1
+ /**
2
+ * vibe session — Session management
3
+ *
4
+ * Save broadcasts as replayable sessions, browse sessions, and fork them.
5
+ *
6
+ * Commands:
7
+ * - session save → Save current/recent broadcast as session
8
+ * - session save --room X → Save specific broadcast
9
+ * - session list → List my sessions
10
+ * - session browse → Browse all public sessions
11
+ * - session fork <id> → Fork a session
12
+ * - session view <id> → View session details
13
+ */
14
+
15
+ const config = require('../config');
16
+
17
+ const definition = {
18
+ name: 'vibe_session',
19
+ description: 'Save broadcasts as sessions, list your sessions, fork others\' sessions.',
20
+ inputSchema: {
21
+ type: 'object',
22
+ properties: {
23
+ action: {
24
+ type: 'string',
25
+ enum: ['save', 'list', 'browse', 'fork', 'view'],
26
+ description: 'Action: save, list, browse, fork, or view'
27
+ },
28
+ room: {
29
+ type: 'string',
30
+ description: 'Room ID to save (for save action)'
31
+ },
32
+ id: {
33
+ type: 'string',
34
+ description: 'Session ID (for fork/view action)'
35
+ },
36
+ title: {
37
+ type: 'string',
38
+ description: 'Session title (for save action)'
39
+ },
40
+ visibility: {
41
+ type: 'string',
42
+ enum: ['public', 'unlisted', 'private'],
43
+ description: 'Session visibility (default: public)'
44
+ },
45
+ limit: {
46
+ type: 'number',
47
+ description: 'Number of sessions to show (default: 10)'
48
+ }
49
+ }
50
+ }
51
+ };
52
+
53
+ async function handler(args) {
54
+ const myHandle = config.getHandle();
55
+ const apiUrl = config.getApiUrl();
56
+ const action = args.action || 'list';
57
+
58
+ if (!myHandle && action !== 'browse' && action !== 'view') {
59
+ return { display: '⚠️ Not initialized. Run `vibe init` first.' };
60
+ }
61
+
62
+ // SAVE broadcast as session
63
+ if (action === 'save') {
64
+ // Get room ID from args or from active broadcast
65
+ let roomId = args.room;
66
+
67
+ if (!roomId) {
68
+ // Check for active broadcast
69
+ const broadcast = config.get('broadcast');
70
+ if (broadcast) {
71
+ roomId = broadcast.roomId;
72
+ }
73
+ }
74
+
75
+ if (!roomId) {
76
+ return {
77
+ display: `⚠️ No broadcast to save.\n\n**Options:**\n1. Specify a room: \`vibe session save --room room_xxx\`\n2. Start a broadcast first: \`vibe broadcast start\``
78
+ };
79
+ }
80
+
81
+ const title = args.title || `Session by @${myHandle}`;
82
+ const visibility = args.visibility || 'public';
83
+
84
+ try {
85
+ const response = await fetch(`${apiUrl}/api/sessions`, {
86
+ method: 'POST',
87
+ headers: { 'Content-Type': 'application/json' },
88
+ body: JSON.stringify({
89
+ fromBroadcast: roomId,
90
+ handle: myHandle,
91
+ title,
92
+ visibility
93
+ })
94
+ });
95
+
96
+ const data = await response.json();
97
+
98
+ if (!data.success) {
99
+ if (data.error?.includes('buffer not found')) {
100
+ return {
101
+ display: `⚠️ Broadcast buffer expired.\n\n_Buffers expire 1 hour after broadcast ends. Start a new broadcast to create a session._`
102
+ };
103
+ }
104
+ return { display: `⚠️ Failed to save session: ${data.error}` };
105
+ }
106
+
107
+ const formatDuration = (secs) => {
108
+ if (!secs || secs < 60) return 'under 1 minute';
109
+ if (secs < 3600) return `${Math.floor(secs / 60)} minutes`;
110
+ return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`;
111
+ };
112
+
113
+ // Chapter icons for display
114
+ const chapterIcons = {
115
+ setup: '🔧',
116
+ problem: '❌',
117
+ investigation: '🔍',
118
+ breakthrough: '💡',
119
+ implementation: '⚡',
120
+ ship: '🚀'
121
+ };
122
+
123
+ let display = `✅ **Session Saved**\n\n`;
124
+ display += `**${data.title}**\n\n`;
125
+ display += `📎 ${data.shareUrl}\n\n`;
126
+ display += `**Stats:**\n`;
127
+ display += `- Duration: ${formatDuration(data.stats?.durationSeconds)}\n`;
128
+ display += `- Chunks: ${data.stats?.chunks || 0}\n`;
129
+
130
+ // Show chapters if detected
131
+ const chapters = data.chapters || [];
132
+ if (chapters.length > 0) {
133
+ display += `- Chapters: ${chapters.length}\n\n`;
134
+ display += `📖 **Story Arc:**\n`;
135
+ const chapterFlow = chapters.map(ch => {
136
+ const icon = chapterIcons[ch.type] || '📋';
137
+ return `${icon} ${ch.title}`;
138
+ }).join(' → ');
139
+ display += ` ${chapterFlow}\n\n`;
140
+ } else {
141
+ display += `- Fork points: ${data.stats?.forkPoints || 0}\n\n`;
142
+ }
143
+
144
+ display += `_Share this URL or find it later with \`vibe session list\`_`;
145
+
146
+ return { display };
147
+
148
+ } catch (error) {
149
+ return { display: `## Save Error\n\n${error.message}` };
150
+ }
151
+ }
152
+
153
+ // LIST my sessions
154
+ if (action === 'list') {
155
+ const limit = args.limit || 10;
156
+
157
+ try {
158
+ const response = await fetch(
159
+ `${apiUrl}/api/sessions/browse?author=${myHandle}&limit=${limit}`,
160
+ { headers: { 'Accept': 'application/json' } }
161
+ );
162
+
163
+ const data = await response.json();
164
+
165
+ if (!data.success) {
166
+ return { display: `⚠️ Failed to fetch sessions: ${data.error}` };
167
+ }
168
+
169
+ if (!data.sessions || data.sessions.length === 0) {
170
+ let display = `📼 **No Sessions Yet**\n\n`;
171
+ display += `You haven't saved any sessions.\n\n`;
172
+ display += `**Create your first session:**\n`;
173
+ display += `1. \`vibe broadcast start "What you're building"\`\n`;
174
+ display += `2. Code for a while...\n`;
175
+ display += `3. \`vibe broadcast stop\`\n`;
176
+ display += `4. \`vibe session save\``;
177
+ return { display };
178
+ }
179
+
180
+ const formatDuration = (secs) => {
181
+ if (!secs || secs < 60) return '<1m';
182
+ if (secs < 3600) return `${Math.floor(secs / 60)}m`;
183
+ return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`;
184
+ };
185
+
186
+ const formatDate = (dateStr) => {
187
+ const date = new Date(dateStr);
188
+ return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
189
+ };
190
+
191
+ let display = `📼 **Your Sessions** (${data.sessions.length}${data.hasMore ? '+' : ''})\n\n`;
192
+
193
+ for (const s of data.sessions) {
194
+ const duration = formatDuration(s.duration_seconds);
195
+ const date = formatDate(s.created_at);
196
+ const views = s.views || 0;
197
+ const forks = s.forks || 0;
198
+
199
+ display += `**${s.title}**\n`;
200
+ display += ` ${date} · ${duration}`;
201
+ if (views > 0) display += ` · ${views} views`;
202
+ if (forks > 0) display += ` · ${forks} forks`;
203
+ display += `\n`;
204
+ display += ` → \`vibe session view ${s.id}\`\n\n`;
205
+ }
206
+
207
+ if (data.hasMore) {
208
+ display += `_More sessions available. Use \`--limit 20\` to see more._`;
209
+ }
210
+
211
+ return { display };
212
+
213
+ } catch (error) {
214
+ return { display: `## List Error\n\n${error.message}` };
215
+ }
216
+ }
217
+
218
+ // BROWSE all public sessions
219
+ if (action === 'browse') {
220
+ const limit = args.limit || 10;
221
+
222
+ try {
223
+ const response = await fetch(
224
+ `${apiUrl}/api/sessions/browse?limit=${limit}&sort=popular`,
225
+ { headers: { 'Accept': 'application/json' } }
226
+ );
227
+
228
+ const data = await response.json();
229
+
230
+ if (!data.success) {
231
+ return { display: `⚠️ Failed to fetch sessions: ${data.error}` };
232
+ }
233
+
234
+ if (!data.sessions || data.sessions.length === 0) {
235
+ return {
236
+ display: `📼 No public sessions yet.\n\n_Be the first! Start a broadcast and save it._`
237
+ };
238
+ }
239
+
240
+ const formatDuration = (secs) => {
241
+ if (!secs || secs < 60) return '<1m';
242
+ if (secs < 3600) return `${Math.floor(secs / 60)}m`;
243
+ return `${Math.floor(secs / 3600)}h`;
244
+ };
245
+
246
+ let display = `📼 **Public Sessions** (${data.total} total)\n\n`;
247
+
248
+ for (const s of data.sessions) {
249
+ const duration = formatDuration(s.duration_seconds);
250
+ const views = s.views || 0;
251
+ const forks = s.forks || 0;
252
+
253
+ display += `**${s.title}** by @${s.author_handle}\n`;
254
+ display += ` ${duration}`;
255
+ if (views > 0) display += ` · ${views} views`;
256
+ if (forks > 0) display += ` · ${forks} forks`;
257
+ display += `\n`;
258
+ display += ` → \`vibe session view ${s.id}\`\n\n`;
259
+ }
260
+
261
+ if (data.hasMore) {
262
+ display += `_More sessions available. Use \`--limit 20\` to see more._`;
263
+ }
264
+
265
+ return { display };
266
+
267
+ } catch (error) {
268
+ return { display: `## Browse Error\n\n${error.message}` };
269
+ }
270
+ }
271
+
272
+ // VIEW session details
273
+ if (action === 'view') {
274
+ if (!args.id) {
275
+ return { display: '⚠️ Session ID required. Usage: `vibe session view ses_xxx`' };
276
+ }
277
+
278
+ const sessionId = args.id;
279
+
280
+ try {
281
+ // Use the story endpoint which returns full session details
282
+ const response = await fetch(`${apiUrl}/story/${sessionId}?format=json`);
283
+
284
+ if (!response.ok) {
285
+ return {
286
+ display: `⚠️ Session not found: ${sessionId}\n\n_Check the ID or browse sessions with \`vibe session browse\`_`
287
+ };
288
+ }
289
+
290
+ const data = await response.json();
291
+
292
+ if (!data.success && !data.session) {
293
+ return { display: `⚠️ Failed to fetch session: ${data.error}` };
294
+ }
295
+
296
+ const s = data.session || data.story || data;
297
+
298
+ const formatDuration = (secs) => {
299
+ if (!secs || secs < 60) return 'under 1 minute';
300
+ if (secs < 3600) return `${Math.floor(secs / 60)} minutes`;
301
+ return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`;
302
+ };
303
+
304
+ // Chapter icons for display
305
+ const chapterIcons = {
306
+ setup: '🔧',
307
+ problem: '❌',
308
+ investigation: '🔍',
309
+ breakthrough: '💡',
310
+ implementation: '⚡',
311
+ ship: '🚀'
312
+ };
313
+
314
+ let display = `📼 **${s.title}**\n\n`;
315
+ display += `**Author:** @${s.author || s.author_handle}\n`;
316
+ display += `**Duration:** ${formatDuration(s.durationSeconds || s.duration_seconds)}\n`;
317
+ display += `**Views:** ${s.views || 0}\n`;
318
+ display += `**Forks:** ${s.forks || 0}\n\n`;
319
+
320
+ // Display chapters if available
321
+ const chapters = s.chapters || [];
322
+ if (chapters.length > 0) {
323
+ display += `📖 **Chapters** (${chapters.length})\n`;
324
+ for (let i = 0; i < chapters.length; i++) {
325
+ const ch = chapters[i];
326
+ const icon = chapterIcons[ch.type] || '📋';
327
+ display += ` ${i + 1}. ${icon} **${ch.title}** (${ch.startTime || '0:00'})\n`;
328
+ if (ch.summary) {
329
+ display += ` _${ch.summary.slice(0, 60)}${ch.summary.length > 60 ? '...' : ''}_\n`;
330
+ }
331
+ }
332
+ display += '\n';
333
+ }
334
+
335
+ // Display fork points if available
336
+ const forkPoints = s.forkPoints || [];
337
+ if (forkPoints.length > 0) {
338
+ display += `🔀 **Fork Points** (${forkPoints.length})\n`;
339
+ for (const fp of forkPoints.slice(0, 5)) {
340
+ display += ` - ${fp.label}: ${fp.description || ''}\n`;
341
+ }
342
+ if (forkPoints.length > 5) {
343
+ display += ` _...and ${forkPoints.length - 5} more_\n`;
344
+ }
345
+ display += '\n';
346
+ }
347
+
348
+ if (s.description) {
349
+ display += `**Description:**\n${s.description}\n\n`;
350
+ }
351
+
352
+ if (s.summary) {
353
+ display += `**Summary:**\n${s.summary}\n\n`;
354
+ }
355
+
356
+ display += `**View full session:**\n`;
357
+ display += `https://slashvibe.dev/story/${sessionId}\n\n`;
358
+
359
+ display += `**Actions:**\n`;
360
+ display += `- Fork: \`vibe session fork ${sessionId}\`\n`;
361
+
362
+ return { display };
363
+
364
+ } catch (error) {
365
+ return { display: `## View Error\n\n${error.message}` };
366
+ }
367
+ }
368
+
369
+ // FORK a session
370
+ if (action === 'fork') {
371
+ if (!args.id) {
372
+ return { display: '⚠️ Session ID required. Usage: `vibe session fork ses_xxx`' };
373
+ }
374
+
375
+ const sessionId = args.id;
376
+
377
+ try {
378
+ const response = await fetch(`${apiUrl}/api/sessions/fork`, {
379
+ method: 'POST',
380
+ headers: { 'Content-Type': 'application/json' },
381
+ body: JSON.stringify({
382
+ parentSessionId: sessionId,
383
+ forkerHandle: myHandle,
384
+ forkTo: 'session',
385
+ forkLevel: 'story_only'
386
+ })
387
+ });
388
+
389
+ const data = await response.json();
390
+
391
+ if (!data.success) {
392
+ if (data.error?.includes('not found')) {
393
+ return {
394
+ display: `⚠️ Session not found: ${sessionId}\n\n_Check the ID or browse sessions with \`vibe session browse\`_`
395
+ };
396
+ }
397
+ if (data.error?.includes('private')) {
398
+ return { display: `⚠️ Cannot fork a private session.` };
399
+ }
400
+ return { display: `⚠️ Failed to fork session: ${data.error}` };
401
+ }
402
+
403
+ let display = `🍴 **Session Forked!**\n\n`;
404
+ display += `**From:** ${data.parentSession?.title || sessionId}\n`;
405
+ display += `**By:** @${data.parentSession?.author || 'unknown'}\n\n`;
406
+ display += `**Fork ID:** ${data.forkId}\n\n`;
407
+ display += `**View the story:**\n${data.storyUrl}\n\n`;
408
+ display += `_Learn from this session and build your own version!_`;
409
+
410
+ return { display };
411
+
412
+ } catch (error) {
413
+ return { display: `## Fork Error\n\n${error.message}` };
414
+ }
415
+ }
416
+
417
+ return { display: 'Unknown action. Use: save, list, browse, view, or fork' };
418
+ }
419
+
420
+ module.exports = { definition, handler };
@@ -0,0 +1,128 @@
1
+ /**
2
+ * vibe_session_price - Set session access type and price
3
+ *
4
+ * Configure how others can access your sessions:
5
+ * - public: Free for all
6
+ * - ppv: Pay-per-view ($1-$50)
7
+ * - subscribers_only: Requires active subscription
8
+ * - private: Creator only
9
+ *
10
+ * Examples:
11
+ * - "set session to ppv $5"
12
+ * - "make my session public"
13
+ * - "set session price to $10"
14
+ */
15
+
16
+ const fetch = require('node-fetch');
17
+ const config = require('../config');
18
+
19
+ const definition = {
20
+ name: 'vibe_session_price',
21
+ description: 'Set session access type and price. Options: public (free), ppv ($1-$50), subscribers_only, private.',
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ session_id: {
26
+ type: 'string',
27
+ description: 'Session ID to configure (defaults to current session if available)'
28
+ },
29
+ access_type: {
30
+ type: 'string',
31
+ enum: ['public', 'ppv', 'subscribers_only', 'private'],
32
+ description: 'Access type: public (free), ppv (pay-per-view), subscribers_only, private'
33
+ },
34
+ price_dollars: {
35
+ type: 'number',
36
+ description: 'Price in dollars for PPV access (1-50). Required if access_type is ppv.'
37
+ }
38
+ },
39
+ required: ['access_type']
40
+ }
41
+ };
42
+
43
+ async function handler(args) {
44
+ const { session_id, access_type, price_dollars } = args;
45
+
46
+ if (!config.isInitialized()) {
47
+ return {
48
+ display: 'Run `vibe init` first to set your identity.'
49
+ };
50
+ }
51
+
52
+ // Validate PPV pricing
53
+ if (access_type === 'ppv') {
54
+ if (!price_dollars || price_dollars < 1 || price_dollars > 50) {
55
+ return {
56
+ display: '❌ PPV access requires a price between $1 and $50'
57
+ };
58
+ }
59
+ }
60
+
61
+ const token = config.getToken();
62
+ const apiUrl = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
63
+
64
+ try {
65
+ const response = await fetch(
66
+ `${apiUrl}/api/sessions/access`,
67
+ {
68
+ method: 'POST',
69
+ headers: {
70
+ 'Content-Type': 'application/json',
71
+ 'Authorization': `Bearer ${token}`
72
+ },
73
+ body: JSON.stringify({
74
+ session_id: session_id || undefined,
75
+ access_type,
76
+ ppv_price_dollars: price_dollars || undefined
77
+ })
78
+ }
79
+ );
80
+
81
+ const result = await response.json();
82
+
83
+ if (!response.ok) {
84
+ return {
85
+ display: `❌ ${result.error || 'Failed to update session access'}`
86
+ };
87
+ }
88
+
89
+ // Format success message
90
+ let accessDesc;
91
+ switch (access_type) {
92
+ case 'public':
93
+ accessDesc = '🌐 Public - Free for everyone';
94
+ break;
95
+ case 'ppv':
96
+ accessDesc = `💵 Pay-Per-View - $${price_dollars}`;
97
+ break;
98
+ case 'subscribers_only':
99
+ accessDesc = '⭐ Subscribers Only';
100
+ break;
101
+ case 'private':
102
+ accessDesc = '🔒 Private - Only you';
103
+ break;
104
+ }
105
+
106
+ let display = `
107
+ ✅ Session Access Updated
108
+
109
+ Session: ${result.session_id}
110
+ Access: ${accessDesc}`;
111
+
112
+ if (access_type === 'ppv') {
113
+ display += `\nPrice: $${price_dollars}`;
114
+ display += '\n\nViewers will need to purchase access to see this session.';
115
+ } else if (access_type === 'subscribers_only') {
116
+ display += '\n\nOnly your subscribers can view this session.';
117
+ }
118
+
119
+ return { display: display.trim(), data: result };
120
+
121
+ } catch (error) {
122
+ return {
123
+ display: `❌ Failed to update session access: ${error.message}`
124
+ };
125
+ }
126
+ }
127
+
128
+ module.exports = { definition, handler };
package/tools/settings.js CHANGED
@@ -6,13 +6,15 @@
6
6
  * - guided: on | off (dashboard mode)
7
7
  * - github_activity: on | off (show GitHub shipping status)
8
8
  * - github_activity_privacy: full | status_only | off
9
+ * - mute: 1h | 2h | 4h | forever | off (merged from vibe_mute)
10
+ * - auto_context: on | off (ambient work context gathering)
9
11
  */
10
12
 
11
13
  const config = require('../config');
12
14
 
13
15
  const definition = {
14
16
  name: 'vibe_settings',
15
- description: 'Configure /vibe preferences. Set notification level, toggle guided mode, or enable GitHub activity signals.',
17
+ description: 'Configure /vibe preferences. Set notification level, toggle guided mode, enable GitHub activity signals, or mute alerts.',
16
18
  inputSchema: {
17
19
  type: 'object',
18
20
  properties: {
@@ -33,6 +35,14 @@ const definition = {
33
35
  type: 'string',
34
36
  enum: ['full', 'status_only', 'off'],
35
37
  description: 'Privacy level: full (repos/commits), status_only (just badge), off'
38
+ },
39
+ mute: {
40
+ type: 'string',
41
+ description: 'Mute presence alerts: "1h", "2h", "4h", "forever", or "off" to unmute'
42
+ },
43
+ auto_context: {
44
+ type: 'boolean',
45
+ description: 'Enable/disable automatic work context gathering (git branch, recent commits shown in presence)'
36
46
  }
37
47
  }
38
48
  }
@@ -65,12 +75,68 @@ async function handler(args) {
65
75
  changes.push('github activity privacy → **' + args.github_activity_privacy + '**');
66
76
  }
67
77
 
78
+ // Handle mute setting (merged from vibe_mute)
79
+ if (args.mute) {
80
+ const duration = args.mute;
81
+
82
+ // UNMUTE
83
+ if (duration === 'off' || duration === 'unmute') {
84
+ config.set('mutedUntil', null);
85
+ config.set('focusMode', false);
86
+ changes.push('alerts → **unmuted**');
87
+ }
88
+ // MUTE FOREVER
89
+ else if (duration === 'forever' || duration === 'disable') {
90
+ config.set('presenceAgentEnabled', false);
91
+ config.set('mutedUntil', null);
92
+ changes.push('alerts → **disabled**');
93
+ }
94
+ // PARSE DURATION
95
+ else {
96
+ let ms = 0;
97
+ const match = duration.match(/^(\d+)(h|m|min|hour|hours|minutes)$/);
98
+
99
+ if (match) {
100
+ const amount = parseInt(match[1]);
101
+ const unit = match[2];
102
+
103
+ if (unit === 'h' || unit === 'hour' || unit === 'hours') {
104
+ ms = amount * 60 * 60 * 1000;
105
+ } else if (unit === 'm' || unit === 'min' || unit === 'minutes') {
106
+ ms = amount * 60 * 1000;
107
+ }
108
+ } else {
109
+ // Default to 1 hour if can't parse
110
+ ms = 60 * 60 * 1000;
111
+ }
112
+
113
+ const mutedUntil = Date.now() + ms;
114
+ config.set('mutedUntil', mutedUntil);
115
+
116
+ const until = new Date(mutedUntil).toLocaleTimeString();
117
+ const durationText = ms >= 3600000
118
+ ? `${Math.floor(ms / 3600000)} hour${Math.floor(ms / 3600000) > 1 ? 's' : ''}`
119
+ : `${Math.floor(ms / 60000)} minutes`;
120
+
121
+ changes.push(`alerts → **muted for ${durationText}** (until ${until})`);
122
+ }
123
+ }
124
+
125
+ // Update auto context if provided
126
+ if (args.auto_context !== undefined) {
127
+ config.set('autoContext', args.auto_context);
128
+ changes.push('auto context → **' + (args.auto_context ? 'on' : 'off') + '**');
129
+ }
130
+
68
131
  // If no args, show current settings
69
132
  if (changes.length === 0) {
70
133
  const notifications = config.getNotifications();
71
134
  const guided = config.getGuidedMode();
72
135
  const githubActivity = config.getGithubActivityEnabled();
73
136
  const githubPrivacy = config.getGithubActivityPrivacy();
137
+ const mutedUntil = config.get('mutedUntil');
138
+ const presenceEnabled = config.get('presenceAgentEnabled') !== false;
139
+ const autoContext = config.get('autoContext', true); // Default to true
74
140
 
75
141
  const notifyDesc = {
76
142
  all: 'All notifications (messages, mentions, presence)',
@@ -84,21 +150,43 @@ async function handler(args) {
84
150
  off: 'Disabled'
85
151
  };
86
152
 
153
+ // Determine mute status
154
+ let muteStatus = 'off';
155
+ let muteDesc = 'Alerts enabled';
156
+ if (!presenceEnabled) {
157
+ muteStatus = 'disabled';
158
+ muteDesc = 'Presence alerts permanently disabled';
159
+ } else if (mutedUntil) {
160
+ const remaining = mutedUntil - Date.now();
161
+ if (remaining > 0) {
162
+ const mins = Math.floor(remaining / 60000);
163
+ const hours = Math.floor(mins / 60);
164
+ muteStatus = hours > 0 ? `${hours}h ${mins % 60}m` : `${mins}m`;
165
+ muteDesc = `Muted until ${new Date(mutedUntil).toLocaleTimeString()}`;
166
+ }
167
+ }
168
+
87
169
  return {
88
170
  display: '## /vibe Settings\n\n' +
89
171
  '**Notifications:** ' + notifications + '\n' +
90
172
  '_' + notifyDesc[notifications] + '_\n\n' +
91
173
  '**Guided Mode:** ' + (guided ? 'on' : 'off') + '\n' +
92
174
  '_' + (guided ? 'Shows dashboard menus' : 'Freeform mode') + '_\n\n' +
175
+ '**Alert Mute:** ' + muteStatus + '\n' +
176
+ '_' + muteDesc + '_\n\n' +
93
177
  '**GitHub Activity:** ' + (githubActivity ? 'on' : 'off') + '\n' +
94
178
  '_' + (githubActivity ? 'Shows shipping status from your GitHub commits' : 'Not sharing GitHub activity') + '_\n\n' +
95
179
  (githubActivity ? '**GitHub Privacy:** ' + githubPrivacy + '\n_' + privacyDesc[githubPrivacy] + '_\n\n' : '') +
180
+ '**Auto Context:** ' + (autoContext ? 'on' : 'off') + '\n' +
181
+ '_' + (autoContext ? 'Git branch & work summary shared in presence' : 'Work context not auto-shared') + '_\n\n' +
96
182
  '---\n\n' +
97
183
  '**Change settings:**\n' +
98
184
  '• "set notifications to mentions" — less noisy\n' +
99
185
  '• "turn off guided mode" — no menus\n' +
186
+ '• "mute for 2h" — silence alerts temporarily\n' +
100
187
  '• "enable github activity" — show when you\'re shipping\n' +
101
- '• "set github privacy to status_only" — just badge, no details'
188
+ '• "set github privacy to status_only" — just badge, no details\n' +
189
+ '• "disable auto context" — stop sharing git branch/work'
102
190
  };
103
191
  }
104
192