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.
- package/README.md +47 -252
- package/analytics.js +107 -0
- package/auth-store.js +148 -0
- package/auto-update.js +130 -0
- package/bridges/bridge-monitor.js +388 -0
- package/bridges/discord-bot.js +431 -0
- package/bridges/farcaster.js +299 -0
- package/bridges/telegram.js +261 -0
- package/bridges/webhook-health.js +420 -0
- package/bridges/webhook-server.js +437 -0
- package/bridges/whatsapp.js +441 -0
- package/bridges/x-webhook.js +423 -0
- package/config.js +27 -15
- package/games/arcade.js +406 -0
- package/games/chess.js +451 -0
- package/games/colorguess.js +343 -0
- package/games/crossword-words.js +171 -0
- package/games/crossword.js +461 -0
- package/games/drawing.js +347 -0
- package/games/gameroulette.js +300 -0
- package/games/gamerouter.js +336 -0
- package/games/gamestatus.js +337 -0
- package/games/guessnumber.js +209 -0
- package/games/hangman.js +279 -0
- package/games/memory.js +338 -0
- package/games/multiplayer-tictactoe.js +389 -0
- package/games/pixelart.js +399 -0
- package/games/quickduel.js +354 -0
- package/games/riddle.js +371 -0
- package/games/rockpaperscissors.js +291 -0
- package/games/snake.js +406 -0
- package/games/storybuilder.js +343 -0
- package/games/tictactoe.js +345 -0
- package/games/twentyquestions.js +286 -0
- package/games/twotruths.js +207 -0
- package/games/werewolf.js +508 -0
- package/games/wordassociation.js +247 -0
- package/games/wordchain.js +135 -0
- package/index.js +116 -159
- package/intelligence/index.js +9 -2
- package/intelligence/interests.js +369 -0
- package/notification-emitter.js +77 -0
- package/notify.js +5 -1
- package/package.json +21 -16
- package/prompts.js +1 -1
- package/protocol/index.js +73 -0
- package/setup.js +480 -0
- package/smart-inbox.js +276 -0
- package/store/api.js +536 -215
- package/store/profiles.js +160 -12
- package/tools/_actions.js +362 -21
- package/tools/_discovery.js +119 -26
- package/tools/_shared/index.js +64 -0
- package/tools/_shared.js +234 -0
- package/tools/_work-context.js +338 -0
- package/tools/_work-context.manual-test.js +199 -0
- package/tools/_work-context.test.js +260 -0
- package/tools/activity.js +220 -0
- package/tools/analytics.js +191 -0
- package/tools/approve.js +197 -0
- package/tools/artifact-create.js +14 -3
- package/tools/artifacts-price.js +107 -0
- package/tools/available.js +120 -0
- package/tools/broadcast.js +325 -0
- package/tools/chat.js +202 -0
- package/tools/collaborative-drawing.js +1 -1
- package/tools/connection-status.js +178 -0
- package/tools/discover.js +350 -34
- package/tools/dm.js +80 -8
- package/tools/earnings.js +126 -0
- package/tools/feed.js +35 -4
- package/tools/follow.js +224 -0
- package/tools/friends.js +207 -0
- package/tools/gig-browse.js +206 -0
- package/tools/gig-complete.js +144 -0
- package/tools/health.js +87 -0
- package/tools/help.js +3 -3
- package/tools/idea.js +9 -2
- package/tools/inbox.js +289 -105
- package/tools/init.js +131 -34
- package/tools/invite.js +15 -4
- package/tools/leaderboard.js +117 -0
- package/tools/lib/git-apply.js +206 -0
- package/tools/lib/git-bundle.js +407 -0
- package/tools/migrate.js +3 -3
- package/tools/multiplayer-game.js +1 -1
- package/tools/onboarding.js +7 -7
- package/tools/open.js +143 -12
- package/tools/party-game.js +1 -1
- package/tools/plan.js +225 -0
- package/tools/proof-of-work.js +144 -0
- package/tools/reply.js +166 -0
- package/tools/report.js +1 -1
- package/tools/request.js +17 -3
- package/tools/schedule.js +367 -0
- package/tools/search-messages.js +123 -0
- package/tools/session.js +467 -0
- package/tools/session_price.js +128 -0
- package/tools/settings.js +90 -2
- package/tools/ship.js +30 -7
- package/tools/smart-check.js +201 -0
- package/tools/start.js +147 -12
- package/tools/status.js +53 -6
- package/tools/streak.js +147 -0
- package/tools/stuck.js +297 -0
- package/tools/subscribe.js +148 -0
- package/tools/subscriptions.js +134 -0
- package/tools/suggest-tags.js +6 -8
- package/tools/tag-suggestions.js +1 -1
- package/tools/tip.js +150 -77
- package/tools/token.js +4 -4
- package/tools/update.js +1 -1
- package/tools/wallet.js +221 -79
- package/tools/watch.js +157 -0
- package/tools/who.js +30 -1
- package/tools/withdraw.js +145 -0
- package/tools/work-summary.js +96 -0
- package/version.json +10 -8
- package/LICENSE +0 -21
- package/store/sqlite.js +0 -347
- /package/tools/{auto-suggest-connections.js → _deprecated/auto-suggest-connections.js} +0 -0
- /package/tools/{away.js → _deprecated/away.js} +0 -0
- /package/tools/{back.js → _deprecated/back.js} +0 -0
- /package/tools/{bootstrap-skills.js → _deprecated/bootstrap-skills.js} +0 -0
- /package/tools/{bridge-dashboard.js → _deprecated/bridge-dashboard.js} +0 -0
- /package/tools/{bridge-health.js → _deprecated/bridge-health.js} +0 -0
- /package/tools/{bridge-live.js → _deprecated/bridge-live.js} +0 -0
- /package/tools/{bridges.js → _deprecated/bridges.js} +0 -0
- /package/tools/{colorguess.js → _deprecated/colorguess.js} +0 -0
- /package/tools/{discover-insights.js → _deprecated/discover-insights.js} +0 -0
- /package/tools/{discover-momentum.js → _deprecated/discover-momentum.js} +0 -0
- /package/tools/{discovery-analytics.js → _deprecated/discovery-analytics.js} +0 -0
- /package/tools/{discovery-auto-suggest.js → _deprecated/discovery-auto-suggest.js} +0 -0
- /package/tools/{discovery-bootstrap.js → _deprecated/discovery-bootstrap.js} +0 -0
- /package/tools/{discovery-daily.js → _deprecated/discovery-daily.js} +0 -0
- /package/tools/{discovery-dashboard.js → _deprecated/discovery-dashboard.js} +0 -0
- /package/tools/{discovery-digest.js → _deprecated/discovery-digest.js} +0 -0
- /package/tools/{discovery-hub.js → _deprecated/discovery-hub.js} +0 -0
- /package/tools/{discovery-insights.js → _deprecated/discovery-insights.js} +0 -0
- /package/tools/{discovery-momentum.js → _deprecated/discovery-momentum.js} +0 -0
- /package/tools/{discovery-monitor.js → _deprecated/discovery-monitor.js} +0 -0
- /package/tools/{discovery-proactive.js → _deprecated/discovery-proactive.js} +0 -0
- /package/tools/{draw.js → _deprecated/draw.js} +0 -0
- /package/tools/{farcaster.js → _deprecated/farcaster.js} +0 -0
- /package/tools/{forget.js → _deprecated/forget.js} +0 -0
- /package/tools/{games-catalog.js → _deprecated/games-catalog.js} +0 -0
- /package/tools/{games.js → _deprecated/games.js} +0 -0
- /package/tools/{guessnumber.js → _deprecated/guessnumber.js} +0 -0
- /package/tools/{hangman.js → _deprecated/hangman.js} +0 -0
- /package/tools/{multiplayer-tictactoe.js → _deprecated/multiplayer-tictactoe.js} +0 -0
- /package/tools/{mute.js → _deprecated/mute.js} +0 -0
- /package/tools/{recall.js → _deprecated/recall.js} +0 -0
- /package/tools/{remember.js → _deprecated/remember.js} +0 -0
- /package/tools/{riddle.js → _deprecated/riddle.js} +0 -0
- /package/tools/{run-bootstrap.js → _deprecated/run-bootstrap.js} +0 -0
- /package/tools/{skills-analytics.js → _deprecated/skills-analytics.js} +0 -0
- /package/tools/{skills-bootstrap.js → _deprecated/skills-bootstrap.js} +0 -0
- /package/tools/{skills-dashboard.js → _deprecated/skills-dashboard.js} +0 -0
- /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
- /package/tools/{skills.js → _deprecated/skills.js} +0 -0
- /package/tools/{smart-intro.js → _deprecated/smart-intro.js} +0 -0
- /package/tools/{storybuilder.js → _deprecated/storybuilder.js} +0 -0
- /package/tools/{telegram-bot.js → _deprecated/telegram-bot.js} +0 -0
- /package/tools/{telegram-setup.js → _deprecated/telegram-setup.js} +0 -0
- /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
- /package/tools/{twentyquestions.js → _deprecated/twentyquestions.js} +0 -0
- /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
package/tools/session.js
ADDED
|
@@ -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 };
|