slashvibe-mcp 0.3.20 → 0.3.21

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 (167) hide show
  1. package/README.md +47 -252
  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 +116 -159
  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 +21 -16
  45. package/prompts.js +1 -1
  46. package/protocol/index.js +73 -0
  47. package/setup.js +480 -0
  48. package/smart-inbox.js +276 -0
  49. package/store/api.js +536 -215
  50. package/store/profiles.js +160 -12
  51. package/tools/_actions.js +362 -21
  52. package/tools/_discovery.js +119 -26
  53. package/tools/_shared/index.js +64 -0
  54. package/tools/_shared.js +234 -0
  55. package/tools/_work-context.js +338 -0
  56. package/tools/_work-context.manual-test.js +199 -0
  57. package/tools/_work-context.test.js +260 -0
  58. package/tools/activity.js +220 -0
  59. package/tools/analytics.js +191 -0
  60. package/tools/approve.js +197 -0
  61. package/tools/artifact-create.js +14 -3
  62. package/tools/artifacts-price.js +107 -0
  63. package/tools/available.js +120 -0
  64. package/tools/broadcast.js +325 -0
  65. package/tools/chat.js +202 -0
  66. package/tools/collaborative-drawing.js +1 -1
  67. package/tools/connection-status.js +178 -0
  68. package/tools/discover.js +350 -34
  69. package/tools/dm.js +80 -8
  70. package/tools/earnings.js +126 -0
  71. package/tools/feed.js +35 -4
  72. package/tools/follow.js +224 -0
  73. package/tools/friends.js +207 -0
  74. package/tools/gig-browse.js +206 -0
  75. package/tools/gig-complete.js +144 -0
  76. package/tools/health.js +87 -0
  77. package/tools/help.js +3 -3
  78. package/tools/idea.js +9 -2
  79. package/tools/inbox.js +289 -105
  80. package/tools/init.js +131 -34
  81. package/tools/invite.js +15 -4
  82. package/tools/leaderboard.js +117 -0
  83. package/tools/lib/git-apply.js +206 -0
  84. package/tools/lib/git-bundle.js +407 -0
  85. package/tools/migrate.js +3 -3
  86. package/tools/multiplayer-game.js +1 -1
  87. package/tools/onboarding.js +7 -7
  88. package/tools/open.js +143 -12
  89. package/tools/party-game.js +1 -1
  90. package/tools/plan.js +225 -0
  91. package/tools/proof-of-work.js +144 -0
  92. package/tools/reply.js +166 -0
  93. package/tools/report.js +1 -1
  94. package/tools/request.js +17 -3
  95. package/tools/schedule.js +367 -0
  96. package/tools/search-messages.js +123 -0
  97. package/tools/session.js +467 -0
  98. package/tools/session_price.js +128 -0
  99. package/tools/settings.js +90 -2
  100. package/tools/ship.js +30 -7
  101. package/tools/smart-check.js +201 -0
  102. package/tools/start.js +147 -12
  103. package/tools/status.js +53 -6
  104. package/tools/streak.js +147 -0
  105. package/tools/stuck.js +297 -0
  106. package/tools/subscribe.js +148 -0
  107. package/tools/subscriptions.js +134 -0
  108. package/tools/suggest-tags.js +6 -8
  109. package/tools/tag-suggestions.js +1 -1
  110. package/tools/tip.js +150 -77
  111. package/tools/token.js +4 -4
  112. package/tools/update.js +1 -1
  113. package/tools/wallet.js +221 -79
  114. package/tools/watch.js +157 -0
  115. package/tools/who.js +30 -1
  116. package/tools/withdraw.js +145 -0
  117. package/tools/work-summary.js +96 -0
  118. package/version.json +10 -8
  119. package/LICENSE +0 -21
  120. package/store/sqlite.js +0 -347
  121. /package/tools/{auto-suggest-connections.js → _deprecated/auto-suggest-connections.js} +0 -0
  122. /package/tools/{away.js → _deprecated/away.js} +0 -0
  123. /package/tools/{back.js → _deprecated/back.js} +0 -0
  124. /package/tools/{bootstrap-skills.js → _deprecated/bootstrap-skills.js} +0 -0
  125. /package/tools/{bridge-dashboard.js → _deprecated/bridge-dashboard.js} +0 -0
  126. /package/tools/{bridge-health.js → _deprecated/bridge-health.js} +0 -0
  127. /package/tools/{bridge-live.js → _deprecated/bridge-live.js} +0 -0
  128. /package/tools/{bridges.js → _deprecated/bridges.js} +0 -0
  129. /package/tools/{colorguess.js → _deprecated/colorguess.js} +0 -0
  130. /package/tools/{discover-insights.js → _deprecated/discover-insights.js} +0 -0
  131. /package/tools/{discover-momentum.js → _deprecated/discover-momentum.js} +0 -0
  132. /package/tools/{discovery-analytics.js → _deprecated/discovery-analytics.js} +0 -0
  133. /package/tools/{discovery-auto-suggest.js → _deprecated/discovery-auto-suggest.js} +0 -0
  134. /package/tools/{discovery-bootstrap.js → _deprecated/discovery-bootstrap.js} +0 -0
  135. /package/tools/{discovery-daily.js → _deprecated/discovery-daily.js} +0 -0
  136. /package/tools/{discovery-dashboard.js → _deprecated/discovery-dashboard.js} +0 -0
  137. /package/tools/{discovery-digest.js → _deprecated/discovery-digest.js} +0 -0
  138. /package/tools/{discovery-hub.js → _deprecated/discovery-hub.js} +0 -0
  139. /package/tools/{discovery-insights.js → _deprecated/discovery-insights.js} +0 -0
  140. /package/tools/{discovery-momentum.js → _deprecated/discovery-momentum.js} +0 -0
  141. /package/tools/{discovery-monitor.js → _deprecated/discovery-monitor.js} +0 -0
  142. /package/tools/{discovery-proactive.js → _deprecated/discovery-proactive.js} +0 -0
  143. /package/tools/{draw.js → _deprecated/draw.js} +0 -0
  144. /package/tools/{farcaster.js → _deprecated/farcaster.js} +0 -0
  145. /package/tools/{forget.js → _deprecated/forget.js} +0 -0
  146. /package/tools/{games-catalog.js → _deprecated/games-catalog.js} +0 -0
  147. /package/tools/{games.js → _deprecated/games.js} +0 -0
  148. /package/tools/{guessnumber.js → _deprecated/guessnumber.js} +0 -0
  149. /package/tools/{hangman.js → _deprecated/hangman.js} +0 -0
  150. /package/tools/{multiplayer-tictactoe.js → _deprecated/multiplayer-tictactoe.js} +0 -0
  151. /package/tools/{mute.js → _deprecated/mute.js} +0 -0
  152. /package/tools/{recall.js → _deprecated/recall.js} +0 -0
  153. /package/tools/{remember.js → _deprecated/remember.js} +0 -0
  154. /package/tools/{riddle.js → _deprecated/riddle.js} +0 -0
  155. /package/tools/{run-bootstrap.js → _deprecated/run-bootstrap.js} +0 -0
  156. /package/tools/{skills-analytics.js → _deprecated/skills-analytics.js} +0 -0
  157. /package/tools/{skills-bootstrap.js → _deprecated/skills-bootstrap.js} +0 -0
  158. /package/tools/{skills-dashboard.js → _deprecated/skills-dashboard.js} +0 -0
  159. /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
  160. /package/tools/{skills.js → _deprecated/skills.js} +0 -0
  161. /package/tools/{smart-intro.js → _deprecated/smart-intro.js} +0 -0
  162. /package/tools/{storybuilder.js → _deprecated/storybuilder.js} +0 -0
  163. /package/tools/{telegram-bot.js → _deprecated/telegram-bot.js} +0 -0
  164. /package/tools/{telegram-setup.js → _deprecated/telegram-setup.js} +0 -0
  165. /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
  166. /package/tools/{twentyquestions.js → _deprecated/twentyquestions.js} +0 -0
  167. /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
@@ -0,0 +1,467 @@
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 (story only)
12
+ * - session fork <id> --full → Fork with git bundle (get actual code)
13
+ * - session view <id> → View session details
14
+ */
15
+
16
+ const config = require('../config');
17
+ const gitApply = require('./lib/git-apply');
18
+
19
+ const definition = {
20
+ name: 'vibe_session',
21
+ description: 'Save broadcasts as sessions, list your sessions, fork others\' sessions.',
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ action: {
26
+ type: 'string',
27
+ enum: ['save', 'list', 'browse', 'fork', 'view'],
28
+ description: 'Action: save, list, browse, fork, or view'
29
+ },
30
+ room: {
31
+ type: 'string',
32
+ description: 'Room ID to save (for save action)'
33
+ },
34
+ id: {
35
+ type: 'string',
36
+ description: 'Session ID (for fork/view action)'
37
+ },
38
+ title: {
39
+ type: 'string',
40
+ description: 'Session title (for save action)'
41
+ },
42
+ visibility: {
43
+ type: 'string',
44
+ enum: ['public', 'unlisted', 'private'],
45
+ description: 'Session visibility (default: public)'
46
+ },
47
+ limit: {
48
+ type: 'number',
49
+ description: 'Number of sessions to show (default: 10)'
50
+ },
51
+ full: {
52
+ type: 'boolean',
53
+ description: 'For fork: download git bundle and apply locally (default: false)'
54
+ },
55
+ branch: {
56
+ type: 'string',
57
+ description: 'For fork --full: branch name for applied code (default: forked-session)'
58
+ }
59
+ }
60
+ }
61
+ };
62
+
63
+ async function handler(args) {
64
+ const myHandle = config.getHandle();
65
+ const apiUrl = config.getApiUrl();
66
+ const action = args.action || 'list';
67
+
68
+ if (!myHandle && action !== 'browse' && action !== 'view') {
69
+ return { display: '⚠️ Not initialized. Run `vibe init` first.' };
70
+ }
71
+
72
+ // SAVE broadcast as session
73
+ if (action === 'save') {
74
+ // Get room ID from args or from active broadcast
75
+ let roomId = args.room;
76
+
77
+ if (!roomId) {
78
+ // Check for active broadcast
79
+ const broadcast = config.get('broadcast');
80
+ if (broadcast) {
81
+ roomId = broadcast.roomId;
82
+ }
83
+ }
84
+
85
+ if (!roomId) {
86
+ return {
87
+ 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\``
88
+ };
89
+ }
90
+
91
+ const title = args.title || `Session by @${myHandle}`;
92
+ const visibility = args.visibility || 'public';
93
+
94
+ try {
95
+ const response = await fetch(`${apiUrl}/api/sessions`, {
96
+ method: 'POST',
97
+ headers: { 'Content-Type': 'application/json' },
98
+ body: JSON.stringify({
99
+ fromBroadcast: roomId,
100
+ handle: myHandle,
101
+ title,
102
+ visibility
103
+ })
104
+ });
105
+
106
+ const data = await response.json();
107
+
108
+ if (!data.success) {
109
+ if (data.error?.includes('buffer not found')) {
110
+ return {
111
+ display: `⚠️ Broadcast buffer expired.\n\n_Buffers expire 1 hour after broadcast ends. Start a new broadcast to create a session._`
112
+ };
113
+ }
114
+ return { display: `⚠️ Failed to save session: ${data.error}` };
115
+ }
116
+
117
+ const formatDuration = (secs) => {
118
+ if (!secs || secs < 60) return 'under 1 minute';
119
+ if (secs < 3600) return `${Math.floor(secs / 60)} minutes`;
120
+ return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`;
121
+ };
122
+
123
+ // Chapter icons for display
124
+ const chapterIcons = {
125
+ setup: '🔧',
126
+ problem: '❌',
127
+ investigation: '🔍',
128
+ breakthrough: '💡',
129
+ implementation: '⚡',
130
+ ship: '🚀'
131
+ };
132
+
133
+ let display = `✅ **Session Saved**\n\n`;
134
+ display += `**${data.title}**\n\n`;
135
+ display += `📎 ${data.shareUrl}\n\n`;
136
+ display += `**Stats:**\n`;
137
+ display += `- Duration: ${formatDuration(data.stats?.durationSeconds)}\n`;
138
+ display += `- Chunks: ${data.stats?.chunks || 0}\n`;
139
+
140
+ // Show chapters if detected
141
+ const chapters = data.chapters || [];
142
+ if (chapters.length > 0) {
143
+ display += `- Chapters: ${chapters.length}\n\n`;
144
+ display += `📖 **Story Arc:**\n`;
145
+ const chapterFlow = chapters.map(ch => {
146
+ const icon = chapterIcons[ch.type] || '📋';
147
+ return `${icon} ${ch.title}`;
148
+ }).join(' → ');
149
+ display += ` ${chapterFlow}\n\n`;
150
+ } else {
151
+ display += `- Fork points: ${data.stats?.forkPoints || 0}\n\n`;
152
+ }
153
+
154
+ display += `_Share this URL or find it later with \`vibe session list\`_`;
155
+
156
+ return { display };
157
+
158
+ } catch (error) {
159
+ return { display: `## Save Error\n\n${error.message}` };
160
+ }
161
+ }
162
+
163
+ // LIST my sessions
164
+ if (action === 'list') {
165
+ const limit = args.limit || 10;
166
+
167
+ try {
168
+ const response = await fetch(
169
+ `${apiUrl}/api/sessions/browse?author=${myHandle}&limit=${limit}`,
170
+ { headers: { 'Accept': 'application/json' } }
171
+ );
172
+
173
+ const data = await response.json();
174
+
175
+ if (!data.success) {
176
+ return { display: `⚠️ Failed to fetch sessions: ${data.error}` };
177
+ }
178
+
179
+ if (!data.sessions || data.sessions.length === 0) {
180
+ let display = `📼 **No Sessions Yet**\n\n`;
181
+ display += `You haven't saved any sessions.\n\n`;
182
+ display += `**Create your first session:**\n`;
183
+ display += `1. \`vibe broadcast start "What you're building"\`\n`;
184
+ display += `2. Code for a while...\n`;
185
+ display += `3. \`vibe broadcast stop\`\n`;
186
+ display += `4. \`vibe session save\``;
187
+ return { display };
188
+ }
189
+
190
+ const formatDuration = (secs) => {
191
+ if (!secs || secs < 60) return '<1m';
192
+ if (secs < 3600) return `${Math.floor(secs / 60)}m`;
193
+ return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`;
194
+ };
195
+
196
+ const formatDate = (dateStr) => {
197
+ const date = new Date(dateStr);
198
+ return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
199
+ };
200
+
201
+ let display = `📼 **Your Sessions** (${data.sessions.length}${data.hasMore ? '+' : ''})\n\n`;
202
+
203
+ for (const s of data.sessions) {
204
+ const duration = formatDuration(s.duration_seconds);
205
+ const date = formatDate(s.created_at);
206
+ const views = s.views || 0;
207
+ const forks = s.forks || 0;
208
+
209
+ display += `**${s.title}**\n`;
210
+ display += ` ${date} · ${duration}`;
211
+ if (views > 0) display += ` · ${views} views`;
212
+ if (forks > 0) display += ` · ${forks} forks`;
213
+ display += `\n`;
214
+ display += ` → \`vibe session view ${s.id}\`\n\n`;
215
+ }
216
+
217
+ if (data.hasMore) {
218
+ display += `_More sessions available. Use \`--limit 20\` to see more._`;
219
+ }
220
+
221
+ return { display };
222
+
223
+ } catch (error) {
224
+ return { display: `## List Error\n\n${error.message}` };
225
+ }
226
+ }
227
+
228
+ // BROWSE all public sessions
229
+ if (action === 'browse') {
230
+ const limit = args.limit || 10;
231
+
232
+ try {
233
+ const response = await fetch(
234
+ `${apiUrl}/api/sessions/browse?limit=${limit}&sort=popular`,
235
+ { headers: { 'Accept': 'application/json' } }
236
+ );
237
+
238
+ const data = await response.json();
239
+
240
+ if (!data.success) {
241
+ return { display: `⚠️ Failed to fetch sessions: ${data.error}` };
242
+ }
243
+
244
+ if (!data.sessions || data.sessions.length === 0) {
245
+ return {
246
+ display: `📼 No public sessions yet.\n\n_Be the first! Start a broadcast and save it._`
247
+ };
248
+ }
249
+
250
+ const formatDuration = (secs) => {
251
+ if (!secs || secs < 60) return '<1m';
252
+ if (secs < 3600) return `${Math.floor(secs / 60)}m`;
253
+ return `${Math.floor(secs / 3600)}h`;
254
+ };
255
+
256
+ let display = `📼 **Public Sessions** (${data.total} total)\n\n`;
257
+
258
+ for (const s of data.sessions) {
259
+ const duration = formatDuration(s.duration_seconds);
260
+ const views = s.views || 0;
261
+ const forks = s.forks || 0;
262
+
263
+ display += `**${s.title}** by @${s.author_handle}\n`;
264
+ display += ` ${duration}`;
265
+ if (views > 0) display += ` · ${views} views`;
266
+ if (forks > 0) display += ` · ${forks} forks`;
267
+ display += `\n`;
268
+ display += ` → \`vibe session view ${s.id}\`\n\n`;
269
+ }
270
+
271
+ if (data.hasMore) {
272
+ display += `_More sessions available. Use \`--limit 20\` to see more._`;
273
+ }
274
+
275
+ return { display };
276
+
277
+ } catch (error) {
278
+ return { display: `## Browse Error\n\n${error.message}` };
279
+ }
280
+ }
281
+
282
+ // VIEW session details
283
+ if (action === 'view') {
284
+ if (!args.id) {
285
+ return { display: '⚠️ Session ID required. Usage: `vibe session view ses_xxx`' };
286
+ }
287
+
288
+ const sessionId = args.id;
289
+
290
+ try {
291
+ // Use the story endpoint which returns full session details
292
+ const response = await fetch(`${apiUrl}/story/${sessionId}?format=json`);
293
+
294
+ if (!response.ok) {
295
+ return {
296
+ display: `⚠️ Session not found: ${sessionId}\n\n_Check the ID or browse sessions with \`vibe session browse\`_`
297
+ };
298
+ }
299
+
300
+ const data = await response.json();
301
+
302
+ if (!data.success && !data.session) {
303
+ return { display: `⚠️ Failed to fetch session: ${data.error}` };
304
+ }
305
+
306
+ const s = data.session || data.story || data;
307
+
308
+ const formatDuration = (secs) => {
309
+ if (!secs || secs < 60) return 'under 1 minute';
310
+ if (secs < 3600) return `${Math.floor(secs / 60)} minutes`;
311
+ return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`;
312
+ };
313
+
314
+ // Chapter icons for display
315
+ const chapterIcons = {
316
+ setup: '🔧',
317
+ problem: '❌',
318
+ investigation: '🔍',
319
+ breakthrough: '💡',
320
+ implementation: '⚡',
321
+ ship: '🚀'
322
+ };
323
+
324
+ let display = `📼 **${s.title}**\n\n`;
325
+ display += `**Author:** @${s.author || s.author_handle}\n`;
326
+ display += `**Duration:** ${formatDuration(s.durationSeconds || s.duration_seconds)}\n`;
327
+ display += `**Views:** ${s.views || 0}\n`;
328
+ display += `**Forks:** ${s.forks || 0}\n\n`;
329
+
330
+ // Display chapters if available
331
+ const chapters = s.chapters || [];
332
+ if (chapters.length > 0) {
333
+ display += `📖 **Chapters** (${chapters.length})\n`;
334
+ for (let i = 0; i < chapters.length; i++) {
335
+ const ch = chapters[i];
336
+ const icon = chapterIcons[ch.type] || '📋';
337
+ display += ` ${i + 1}. ${icon} **${ch.title}** (${ch.startTime || '0:00'})\n`;
338
+ if (ch.summary) {
339
+ display += ` _${ch.summary.slice(0, 60)}${ch.summary.length > 60 ? '...' : ''}_\n`;
340
+ }
341
+ }
342
+ display += '\n';
343
+ }
344
+
345
+ // Display fork points if available
346
+ const forkPoints = s.forkPoints || [];
347
+ if (forkPoints.length > 0) {
348
+ display += `🔀 **Fork Points** (${forkPoints.length})\n`;
349
+ for (const fp of forkPoints.slice(0, 5)) {
350
+ display += ` - ${fp.label}: ${fp.description || ''}\n`;
351
+ }
352
+ if (forkPoints.length > 5) {
353
+ display += ` _...and ${forkPoints.length - 5} more_\n`;
354
+ }
355
+ display += '\n';
356
+ }
357
+
358
+ if (s.description) {
359
+ display += `**Description:**\n${s.description}\n\n`;
360
+ }
361
+
362
+ if (s.summary) {
363
+ display += `**Summary:**\n${s.summary}\n\n`;
364
+ }
365
+
366
+ display += `**View full session:**\n`;
367
+ display += `https://slashvibe.dev/story/${sessionId}\n\n`;
368
+
369
+ display += `**Actions:**\n`;
370
+ display += `- Fork: \`vibe session fork ${sessionId}\`\n`;
371
+
372
+ return { display };
373
+
374
+ } catch (error) {
375
+ return { display: `## View Error\n\n${error.message}` };
376
+ }
377
+ }
378
+
379
+ // FORK a session
380
+ if (action === 'fork') {
381
+ if (!args.id) {
382
+ return { display: '⚠️ Session ID required. Usage: `vibe session fork ses_xxx`' };
383
+ }
384
+
385
+ const sessionId = args.id;
386
+ const fullFork = args.full === true;
387
+ const branchName = args.branch || 'forked-session';
388
+ const forkLevel = fullFork ? 'full' : 'story_only';
389
+
390
+ try {
391
+ const response = await fetch(`${apiUrl}/api/sessions/fork`, {
392
+ method: 'POST',
393
+ headers: { 'Content-Type': 'application/json' },
394
+ body: JSON.stringify({
395
+ parentSessionId: sessionId,
396
+ forkerHandle: myHandle,
397
+ forkTo: 'session',
398
+ forkLevel
399
+ })
400
+ });
401
+
402
+ const data = await response.json();
403
+
404
+ if (!data.success) {
405
+ if (data.error?.includes('not found')) {
406
+ return {
407
+ display: `⚠️ Session not found: ${sessionId}\n\n_Check the ID or browse sessions with \`vibe session browse\`_`
408
+ };
409
+ }
410
+ if (data.error?.includes('private')) {
411
+ return { display: `⚠️ Cannot fork a private session.` };
412
+ }
413
+ return { display: `⚠️ Failed to fork session: ${data.error}` };
414
+ }
415
+
416
+ let display = `🍴 **Session Forked!**\n\n`;
417
+ display += `**From:** ${data.parentSession?.title || sessionId}\n`;
418
+ display += `**By:** @${data.parentSession?.author || 'unknown'}\n\n`;
419
+ display += `**Fork ID:** ${data.forkId}\n\n`;
420
+
421
+ // Handle full fork with git bundle
422
+ if (fullFork && data.git?.bundleDownloadUrl) {
423
+ display += `📦 **Downloading git bundle...**\n`;
424
+
425
+ const applyResult = await gitApply.applyBundleFromUrl(
426
+ data.git.bundleDownloadUrl,
427
+ branchName
428
+ );
429
+
430
+ if (applyResult.success) {
431
+ display += `✅ **Code applied to branch:** \`${applyResult.branch}\`\n\n`;
432
+ display += `**Git info:**\n`;
433
+ display += `- Branch: ${data.git.branch || 'unknown'}\n`;
434
+ display += `- Commits: ${data.git.initialCommit?.slice(0, 7)}..${data.git.finalCommit?.slice(0, 7)}\n`;
435
+ display += `- Bundle size: ${Math.round(applyResult.size / 1024)}KB\n\n`;
436
+ display += `**Next steps:**\n`;
437
+ display += `\`\`\`\ngit checkout ${applyResult.branch}\n\`\`\`\n`;
438
+ } else {
439
+ display += `⚠️ **Bundle apply failed:** ${applyResult.error}\n\n`;
440
+ display += `_The session was forked, but the code bundle could not be applied._\n`;
441
+ display += `_You can still view the story at the URL below._\n\n`;
442
+ }
443
+ } else if (fullFork && data.git?.hasBundleAvailable && !data.git?.bundleDownloadUrl) {
444
+ display += `⚠️ **Git bundle not available** (R2 storage may not be configured)\n\n`;
445
+ } else if (fullFork && !data.git?.hasBundleAvailable) {
446
+ display += `ℹ️ **No git bundle** for this session (no commits during broadcast)\n\n`;
447
+ }
448
+
449
+ display += `**View the story:**\n${data.storyUrl}\n\n`;
450
+
451
+ if (!fullFork && data.git?.hasBundleAvailable) {
452
+ display += `_💡 Want the actual code? Use \`vibe session fork ${sessionId} --full\`_`;
453
+ } else if (!fullFork) {
454
+ display += `_Learn from this session and build your own version!_`;
455
+ }
456
+
457
+ return { display };
458
+
459
+ } catch (error) {
460
+ return { display: `## Fork Error\n\n${error.message}` };
461
+ }
462
+ }
463
+
464
+ return { display: 'Unknown action. Use: save, list, browse, view, or fork' };
465
+ }
466
+
467
+ 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 };