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/status.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* vibe status — Set your mood/status
|
|
3
|
+
*
|
|
4
|
+
* Now includes away/back functionality (previously vibe_away/vibe_back)
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
7
|
const config = require('../config');
|
|
6
8
|
const store = require('../store');
|
|
7
9
|
const discord = require('../discord');
|
|
8
10
|
const { trackMood } = require('./summarize');
|
|
11
|
+
const { markAway, markBack, getProactiveSummary } = require('../intelligence/proactive');
|
|
9
12
|
|
|
10
13
|
const MOODS = {
|
|
11
14
|
'shipping': '🔥',
|
|
@@ -16,6 +19,8 @@ const MOODS = {
|
|
|
16
19
|
'deep': '🎧',
|
|
17
20
|
'celebrating': '🎉',
|
|
18
21
|
'struggling': '😤',
|
|
22
|
+
'away': '☕', // Merged from vibe_away
|
|
23
|
+
'back': null, // Merged from vibe_back (clears status)
|
|
19
24
|
'clear': null
|
|
20
25
|
};
|
|
21
26
|
|
|
@@ -24,13 +29,17 @@ const SPECIAL_MODES = ['guided', 'freeform'];
|
|
|
24
29
|
|
|
25
30
|
const definition = {
|
|
26
31
|
name: 'vibe_status',
|
|
27
|
-
description: 'Set your mood/status. Options: shipping, thinking, afk, debugging, pairing, deep, celebrating, struggling, clear',
|
|
32
|
+
description: 'Set your mood/status. Options: shipping, thinking, afk, debugging, pairing, deep, celebrating, struggling, away, back, clear',
|
|
28
33
|
inputSchema: {
|
|
29
34
|
type: 'object',
|
|
30
35
|
properties: {
|
|
31
36
|
mood: {
|
|
32
37
|
type: 'string',
|
|
33
|
-
description: 'Your mood (shipping, thinking, afk, debugging, pairing, deep, celebrating, struggling, clear)'
|
|
38
|
+
description: 'Your mood (shipping, thinking, afk, debugging, pairing, deep, celebrating, struggling, away, back, clear)'
|
|
39
|
+
},
|
|
40
|
+
message: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
description: 'Optional away message (only used with away mood, e.g., "grabbing coffee")'
|
|
34
43
|
}
|
|
35
44
|
},
|
|
36
45
|
required: ['mood']
|
|
@@ -44,8 +53,9 @@ async function handler(args) {
|
|
|
44
53
|
};
|
|
45
54
|
}
|
|
46
55
|
|
|
47
|
-
const { mood } = args;
|
|
56
|
+
const { mood, message } = args;
|
|
48
57
|
const moodKey = mood.toLowerCase().replace(/[^a-z]/g, '');
|
|
58
|
+
const handle = config.getHandle();
|
|
49
59
|
|
|
50
60
|
// Handle special modes (guided/freeform)
|
|
51
61
|
if (moodKey === 'guided') {
|
|
@@ -72,18 +82,55 @@ _Say "set status guided" to re-enable interactive menus._`
|
|
|
72
82
|
};
|
|
73
83
|
}
|
|
74
84
|
|
|
85
|
+
// Handle 'away' mood (merged from vibe_away)
|
|
86
|
+
if (moodKey === 'away') {
|
|
87
|
+
const awayMessage = message?.trim();
|
|
88
|
+
|
|
89
|
+
// Validate message length
|
|
90
|
+
if (awayMessage && awayMessage.length > 100) {
|
|
91
|
+
return { display: '⚠️ Away message too long (100 char max)' };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Set away status with optional message
|
|
95
|
+
await store.setAwayStatus(handle, 'away', awayMessage || null);
|
|
96
|
+
markAway();
|
|
97
|
+
|
|
98
|
+
if (awayMessage) {
|
|
99
|
+
return { display: `☕ away — "${awayMessage}"` };
|
|
100
|
+
}
|
|
101
|
+
return { display: '☕ away' };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Handle 'back' mood (merged from vibe_back)
|
|
105
|
+
if (moodKey === 'back') {
|
|
106
|
+
// Clear away status
|
|
107
|
+
await store.clearAwayStatus(handle);
|
|
108
|
+
|
|
109
|
+
let display = '👋 back';
|
|
110
|
+
|
|
111
|
+
// Check if anything happened while away
|
|
112
|
+
const unreadCount = await store.getUnreadCount(handle).catch(() => 0);
|
|
113
|
+
if (unreadCount > 0) {
|
|
114
|
+
display += ` — ${unreadCount} unread`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Mark as back for proactive tracking
|
|
118
|
+
markBack();
|
|
119
|
+
|
|
120
|
+
return { display };
|
|
121
|
+
}
|
|
122
|
+
|
|
75
123
|
if (!MOODS.hasOwnProperty(moodKey)) {
|
|
76
124
|
const options = Object.entries(MOODS)
|
|
77
|
-
.filter(([k, v]) => v)
|
|
125
|
+
.filter(([k, v]) => v && k !== 'back') // Exclude 'back' from list (it's a clear action)
|
|
78
126
|
.map(([k, v]) => `${v} ${k}`)
|
|
79
127
|
.join(', ');
|
|
80
128
|
return {
|
|
81
|
-
display: `Unknown mood. Options: ${options}, or "clear" to remove`
|
|
129
|
+
display: `Unknown mood. Options: ${options}, or "clear"/"back" to remove`
|
|
82
130
|
};
|
|
83
131
|
}
|
|
84
132
|
|
|
85
133
|
const emoji = MOODS[moodKey];
|
|
86
|
-
const handle = config.getHandle();
|
|
87
134
|
|
|
88
135
|
// Update presence with mood via context
|
|
89
136
|
await store.heartbeat(handle, config.getOneLiner(), { mood: emoji });
|
package/tools/streak.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe_streak — View and track your building streak
|
|
3
|
+
*
|
|
4
|
+
* Streaks drive retention through daily habit formation.
|
|
5
|
+
*
|
|
6
|
+
* Badge Tiers:
|
|
7
|
+
* - 7 days: Verified Builder
|
|
8
|
+
* - 14 days: Consistent Builder
|
|
9
|
+
* - 30 days: Dedicated Builder
|
|
10
|
+
* - 60 days: Relentless Builder
|
|
11
|
+
* - 100 days: Legendary Builder
|
|
12
|
+
*
|
|
13
|
+
* Streak Freezes:
|
|
14
|
+
* - Earn 1 freeze every 7 days (max 3)
|
|
15
|
+
* - Auto-used if you miss a day
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const config = require('../config');
|
|
19
|
+
|
|
20
|
+
const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
|
|
21
|
+
|
|
22
|
+
const definition = {
|
|
23
|
+
name: 'vibe_streak',
|
|
24
|
+
description: 'View your building streak, badges, and freezes',
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
properties: {
|
|
28
|
+
user: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'Username to check (default: yourself)'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function requireInit() {
|
|
37
|
+
if (!config.isInitialized()) {
|
|
38
|
+
throw new Error('Not initialized. Run `vibe init` first.');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function handler(args) {
|
|
43
|
+
requireInit();
|
|
44
|
+
|
|
45
|
+
const myHandle = config.getHandle();
|
|
46
|
+
const targetUser = args.user ? args.user.toLowerCase().replace('@', '') : myHandle;
|
|
47
|
+
const isOwnStreak = targetUser === myHandle.toLowerCase();
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const endpoint = `${API_URL}/api/growth/streak?user=${targetUser}`;
|
|
51
|
+
const response = await fetch(endpoint);
|
|
52
|
+
const data = await response.json();
|
|
53
|
+
|
|
54
|
+
if (!data.success) {
|
|
55
|
+
return { display: `\u274c Failed to load streak: ${data.error}` };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const streak = data.streak;
|
|
59
|
+
let display = '';
|
|
60
|
+
|
|
61
|
+
// Header with current streak
|
|
62
|
+
if (isOwnStreak) {
|
|
63
|
+
display += `## Your Building Streak\n\n`;
|
|
64
|
+
} else {
|
|
65
|
+
display += `## @${targetUser}'s Building Streak\n\n`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Big streak number with fire emoji based on length
|
|
69
|
+
const fireEmoji = streak.current >= 30 ? '\ud83d\udd25\ud83d\udd25\ud83d\udd25' :
|
|
70
|
+
streak.current >= 14 ? '\ud83d\udd25\ud83d\udd25' :
|
|
71
|
+
streak.current >= 7 ? '\ud83d\udd25' : '\ud83d\udcaa';
|
|
72
|
+
|
|
73
|
+
display += `### ${fireEmoji} ${streak.current} Day Streak\n\n`;
|
|
74
|
+
|
|
75
|
+
// Status indicators
|
|
76
|
+
if (streak.atRisk) {
|
|
77
|
+
display += `\u26a0\ufe0f **AT RISK** - Ship something today to keep your streak!\n\n`;
|
|
78
|
+
} else if (streak.isActiveToday) {
|
|
79
|
+
display += `\u2705 **Active today** - streak is safe!\n\n`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Stats
|
|
83
|
+
display += `| Stat | Value |\n`;
|
|
84
|
+
display += `|------|-------|\n`;
|
|
85
|
+
display += `| Current Streak | ${streak.current} days |\n`;
|
|
86
|
+
display += `| Longest Streak | ${streak.longest} days |\n`;
|
|
87
|
+
display += `| Freezes Available | ${data.freezesAvailable}\u2744\ufe0f |\n`;
|
|
88
|
+
display += `| Freezes Used | ${streak.freezesUsed || 0} |\n\n`;
|
|
89
|
+
|
|
90
|
+
// Current tier
|
|
91
|
+
if (streak.tier?.current) {
|
|
92
|
+
display += `### Current Tier: ${streak.tier.current.emoji} ${streak.tier.current.label}\n\n`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Next badge progress
|
|
96
|
+
if (data.nextBadge) {
|
|
97
|
+
const progress = Math.round((streak.current / data.nextBadge.daysAway + streak.current) * 100);
|
|
98
|
+
display += `### Next Badge: ${data.nextBadge.emoji} ${data.nextBadge.label}\n`;
|
|
99
|
+
display += `**${data.nextBadge.daysAway} days** to unlock\n\n`;
|
|
100
|
+
} else if (streak.current >= 100) {
|
|
101
|
+
display += `### \ud83c\udf1f All badges unlocked! You're a legend.\n\n`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Earned badges
|
|
105
|
+
if (streak.badges && streak.badges.length > 0) {
|
|
106
|
+
display += `### Earned Badges\n`;
|
|
107
|
+
const badgeInfo = {
|
|
108
|
+
verified_builder: { emoji: '\u2705', label: 'Verified Builder (7d)' },
|
|
109
|
+
consistent_builder: { emoji: '\ud83d\udcaa', label: 'Consistent Builder (14d)' },
|
|
110
|
+
dedicated_builder: { emoji: '\ud83d\udd25', label: 'Dedicated Builder (30d)' },
|
|
111
|
+
relentless_builder: { emoji: '\u26a1', label: 'Relentless Builder (60d)' },
|
|
112
|
+
legendary_builder: { emoji: '\ud83d\udc51', label: 'Legendary Builder (100d)' }
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
for (const badge of streak.badges) {
|
|
116
|
+
const info = badgeInfo[badge] || { emoji: '\ud83c\udfc5', label: badge };
|
|
117
|
+
display += `- ${info.emoji} ${info.label}\n`;
|
|
118
|
+
}
|
|
119
|
+
display += '\n';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 30-day calendar visualization
|
|
123
|
+
if (data.calendar) {
|
|
124
|
+
display += `### Last 30 Days\n`;
|
|
125
|
+
display += '`';
|
|
126
|
+
for (const day of data.calendar) {
|
|
127
|
+
display += day.active ? '\u25a0' : '\u25a1';
|
|
128
|
+
}
|
|
129
|
+
display += '`\n';
|
|
130
|
+
display += '_(\u25a0 = active day)_\n';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Tip for maintaining streak
|
|
134
|
+
if (isOwnStreak && !streak.isActiveToday) {
|
|
135
|
+
display += `\n---\n**Tip:** Ship something or post to the board to record today's activity!`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { display };
|
|
139
|
+
|
|
140
|
+
} catch (e) {
|
|
141
|
+
return {
|
|
142
|
+
display: `## Building Streak\n\n\u274c **Failed to load**\n\nError: ${e.message}`
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = { definition, handler };
|
package/tools/stuck.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe stuck — The Help Signal
|
|
3
|
+
*
|
|
4
|
+
* "I'm stuck" → broadcast to the network → someone helps → karma flows
|
|
5
|
+
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* - vibe stuck → See who needs help
|
|
8
|
+
* - vibe stuck "I'm stuck on X" → Ask for help
|
|
9
|
+
* - vibe stuck offer @user → Offer to help someone
|
|
10
|
+
* - vibe stuck resolve @helper → Mark resolved, give karma
|
|
11
|
+
*
|
|
12
|
+
* This is the symbiosis layer. Helping others is how the rainforest grows.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const config = require('../config');
|
|
16
|
+
const { requireInit } = require('./_shared');
|
|
17
|
+
|
|
18
|
+
const definition = {
|
|
19
|
+
name: 'vibe_stuck',
|
|
20
|
+
description: 'Ask for help when stuck, see who needs help, or offer to help. The symbiosis layer of /vibe.',
|
|
21
|
+
inputSchema: {
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
action: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
enum: ['browse', 'ask', 'offer', 'resolve', 'karma'],
|
|
27
|
+
description: 'Action: browse (see requests), ask (request help), offer (help someone), resolve (mark solved), karma (see helpers)'
|
|
28
|
+
},
|
|
29
|
+
problem: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'What you need help with (for ask action)'
|
|
32
|
+
},
|
|
33
|
+
context: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'Additional context like file, error message (for ask action)'
|
|
36
|
+
},
|
|
37
|
+
tags: {
|
|
38
|
+
type: 'array',
|
|
39
|
+
items: { type: 'string' },
|
|
40
|
+
description: 'Tags like "react", "auth", "debugging" (for ask action)'
|
|
41
|
+
},
|
|
42
|
+
urgency: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
enum: ['low', 'normal', 'high'],
|
|
45
|
+
description: 'How urgent is this? (default: normal)'
|
|
46
|
+
},
|
|
47
|
+
handle: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: 'Handle to offer help to or give karma to (for offer/resolve)'
|
|
50
|
+
},
|
|
51
|
+
helpId: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Help request ID (for offer/resolve)'
|
|
54
|
+
},
|
|
55
|
+
message: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
description: 'Optional message with your offer or thanks'
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
async function handler(args) {
|
|
64
|
+
const initCheck = requireInit();
|
|
65
|
+
if (initCheck) return initCheck;
|
|
66
|
+
|
|
67
|
+
const myHandle = config.getHandle();
|
|
68
|
+
const apiUrl = config.getApiUrl();
|
|
69
|
+
const action = args.action || (args.problem ? 'ask' : 'browse');
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
// BROWSE — See who needs help
|
|
73
|
+
if (action === 'browse') {
|
|
74
|
+
const response = await fetch(`${apiUrl}/api/help?limit=10`);
|
|
75
|
+
const data = await response.json();
|
|
76
|
+
|
|
77
|
+
if (!data.success) {
|
|
78
|
+
return { display: `⚠️ ${data.error}` };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (data.count === 0) {
|
|
82
|
+
return {
|
|
83
|
+
display: `## 🌿 All Clear
|
|
84
|
+
|
|
85
|
+
No one needs help right now. Everyone's in flow!
|
|
86
|
+
|
|
87
|
+
**Want to help when someone does?**
|
|
88
|
+
Check back later or watch for 🆘 signals in presence.
|
|
89
|
+
|
|
90
|
+
**Stuck on something yourself?**
|
|
91
|
+
\`vibe stuck "describe your problem"\``
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let display = `## 🙋 Help Wanted\n\n`;
|
|
96
|
+
display += `${data.count} builder${data.count === 1 ? '' : 's'} could use a hand:\n\n`;
|
|
97
|
+
|
|
98
|
+
for (const req of data.requests) {
|
|
99
|
+
const urgencyIcon = req.urgency === 'high' ? '🔴' : req.urgency === 'low' ? '🟢' : '🟡';
|
|
100
|
+
display += `---\n\n`;
|
|
101
|
+
display += `${urgencyIcon} **@${req.handle}** — ${req.age}\n`;
|
|
102
|
+
display += `> ${req.problem}\n`;
|
|
103
|
+
if (req.context) {
|
|
104
|
+
display += `_Context: ${req.context}_\n`;
|
|
105
|
+
}
|
|
106
|
+
if (req.tags?.length > 0) {
|
|
107
|
+
display += `Tags: ${req.tags.map(t => `\`${t}\``).join(' ')}\n`;
|
|
108
|
+
}
|
|
109
|
+
if (req.offers?.length > 0) {
|
|
110
|
+
display += `💬 ${req.offers.length} offer${req.offers.length === 1 ? '' : 's'}\n`;
|
|
111
|
+
}
|
|
112
|
+
display += `\n→ \`vibe stuck offer --helpId "${req.id}"\`\n\n`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
display += `---\n\n`;
|
|
116
|
+
display += `_Helping others earns karma. Karma is visible on profiles._`;
|
|
117
|
+
|
|
118
|
+
return { display };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ASK — Request help
|
|
122
|
+
if (action === 'ask') {
|
|
123
|
+
if (!args.problem) {
|
|
124
|
+
return {
|
|
125
|
+
display: `## 🆘 Ask for Help
|
|
126
|
+
|
|
127
|
+
**Basic:**
|
|
128
|
+
\`vibe stuck "I'm stuck on authentication"\`
|
|
129
|
+
|
|
130
|
+
**With context:**
|
|
131
|
+
\`vibe stuck "Getting CORS errors" --context "fetch to /api/data" --tags cors,api\`
|
|
132
|
+
|
|
133
|
+
**High urgency:**
|
|
134
|
+
\`vibe stuck "Production is down" --urgency high\`
|
|
135
|
+
|
|
136
|
+
Your request will be visible to others who can help.
|
|
137
|
+
When resolved, you can give karma to the helper.`
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const response = await fetch(`${apiUrl}/api/help`, {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: { 'Content-Type': 'application/json' },
|
|
144
|
+
body: JSON.stringify({
|
|
145
|
+
handle: myHandle,
|
|
146
|
+
problem: args.problem,
|
|
147
|
+
context: args.context,
|
|
148
|
+
tags: args.tags,
|
|
149
|
+
urgency: args.urgency || 'normal'
|
|
150
|
+
})
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const data = await response.json();
|
|
154
|
+
|
|
155
|
+
if (!data.success) {
|
|
156
|
+
return { display: `⚠️ ${data.error}` };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
display: `## 🆘 Help Signal Sent!
|
|
161
|
+
|
|
162
|
+
**Your request:**
|
|
163
|
+
> ${args.problem}
|
|
164
|
+
|
|
165
|
+
Others can now see you need help. They'll DM you if they can assist.
|
|
166
|
+
|
|
167
|
+
Your presence now shows 🆘 so people know you're stuck.
|
|
168
|
+
|
|
169
|
+
**When you get unstuck:**
|
|
170
|
+
\`vibe stuck resolve --helpId "${data.id}" --handle @helper\`
|
|
171
|
+
|
|
172
|
+
This gives karma to whoever helped you. 🌟`
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// OFFER — Offer to help someone
|
|
177
|
+
if (action === 'offer') {
|
|
178
|
+
if (!args.helpId && !args.handle) {
|
|
179
|
+
// Show help requests to choose from
|
|
180
|
+
const response = await fetch(`${apiUrl}/api/help?limit=5`);
|
|
181
|
+
const data = await response.json();
|
|
182
|
+
|
|
183
|
+
if (!data.success || data.count === 0) {
|
|
184
|
+
return { display: `No active help requests right now. Check back later! 🌿` };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let display = `## 🤝 Offer Help\n\n`;
|
|
188
|
+
display += `Choose someone to help:\n\n`;
|
|
189
|
+
|
|
190
|
+
for (const req of data.requests) {
|
|
191
|
+
display += `**@${req.handle}**: ${req.problem.slice(0, 60)}${req.problem.length > 60 ? '...' : ''}\n`;
|
|
192
|
+
display += `→ \`vibe stuck offer --helpId "${req.id}"\`\n\n`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { display };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const response = await fetch(`${apiUrl}/api/help`, {
|
|
199
|
+
method: 'POST',
|
|
200
|
+
headers: { 'Content-Type': 'application/json' },
|
|
201
|
+
body: JSON.stringify({
|
|
202
|
+
action: 'offer',
|
|
203
|
+
handle: myHandle,
|
|
204
|
+
helpId: args.helpId,
|
|
205
|
+
message: args.message
|
|
206
|
+
})
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const data = await response.json();
|
|
210
|
+
|
|
211
|
+
if (!data.success) {
|
|
212
|
+
return { display: `⚠️ ${data.error}` };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
display: `## 🤝 Offer Sent!
|
|
217
|
+
|
|
218
|
+
They'll get a DM that you want to help.
|
|
219
|
+
|
|
220
|
+
**Start the conversation:**
|
|
221
|
+
\`vibe dm @them "Hey, happy to help with that!"\`
|
|
222
|
+
|
|
223
|
+
When they get unstuck, they can give you karma. 🌟`
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// RESOLVE — Mark as resolved and give karma
|
|
228
|
+
if (action === 'resolve') {
|
|
229
|
+
if (!args.helpId) {
|
|
230
|
+
return {
|
|
231
|
+
display: `## ✅ Resolve Help Request
|
|
232
|
+
|
|
233
|
+
**Usage:**
|
|
234
|
+
\`vibe stuck resolve --helpId "help_xxx" --handle @helper\`
|
|
235
|
+
|
|
236
|
+
This marks your request as solved and gives karma to whoever helped.
|
|
237
|
+
|
|
238
|
+
**With a thank you message:**
|
|
239
|
+
\`vibe stuck resolve --helpId "help_xxx" --handle @helper --message "Couldn't have done it without you!"\``
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const response = await fetch(`${apiUrl}/api/help`, {
|
|
244
|
+
method: 'POST',
|
|
245
|
+
headers: { 'Content-Type': 'application/json' },
|
|
246
|
+
body: JSON.stringify({
|
|
247
|
+
action: 'resolve',
|
|
248
|
+
handle: myHandle,
|
|
249
|
+
helpId: args.helpId,
|
|
250
|
+
resolvedBy: args.handle,
|
|
251
|
+
thanks: args.message
|
|
252
|
+
})
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const data = await response.json();
|
|
256
|
+
|
|
257
|
+
if (!data.success) {
|
|
258
|
+
return { display: `⚠️ ${data.error}` };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let display = `## 🎉 Resolved!\n\n`;
|
|
262
|
+
if (data.karmaAwarded && args.handle) {
|
|
263
|
+
display += `**@${args.handle.replace('@', '')}** earned helper karma! 🌟\n\n`;
|
|
264
|
+
display += `They'll get a thank-you DM from you.\n\n`;
|
|
265
|
+
}
|
|
266
|
+
display += `Glad you got unstuck. Back to flow! 🌿`;
|
|
267
|
+
|
|
268
|
+
return { display };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// KARMA — See helper leaderboard
|
|
272
|
+
if (action === 'karma') {
|
|
273
|
+
return {
|
|
274
|
+
display: `## 🌟 Helper Karma
|
|
275
|
+
|
|
276
|
+
Karma is earned when you help someone and they mark it resolved.
|
|
277
|
+
|
|
278
|
+
**How it works:**
|
|
279
|
+
1. Someone posts \`vibe stuck "problem"\`
|
|
280
|
+
2. You offer to help: \`vibe stuck offer --helpId xxx\`
|
|
281
|
+
3. You help them (DM, pair, etc.)
|
|
282
|
+
4. They resolve: \`vibe stuck resolve --helpId xxx --handle @you\`
|
|
283
|
+
5. You earn karma!
|
|
284
|
+
|
|
285
|
+
_Karma leaderboard coming soon._
|
|
286
|
+
_For now, karma is tracked and will appear on profiles._`
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return { display: 'Unknown action. Use: browse, ask, offer, resolve, karma' };
|
|
291
|
+
|
|
292
|
+
} catch (error) {
|
|
293
|
+
return { display: `## Error\n\n${error.message}` };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
module.exports = { definition, handler };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe_subscribe - Subscribe to a creator
|
|
3
|
+
*
|
|
4
|
+
* Subscribe to a creator's plan to access their subscriber-only content.
|
|
5
|
+
* Creates a subscription record and grants access to exclusive sessions.
|
|
6
|
+
*
|
|
7
|
+
* Examples:
|
|
8
|
+
* - "subscribe to @creator"
|
|
9
|
+
* - "join @creator's monthly plan"
|
|
10
|
+
* - "subscribe to seth"
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fetch = require('node-fetch');
|
|
14
|
+
const config = require('../config');
|
|
15
|
+
|
|
16
|
+
const definition = {
|
|
17
|
+
name: 'vibe_subscribe',
|
|
18
|
+
description: 'Subscribe to a creator to access their subscriber-only content.',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
creator_handle: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'Handle of the creator to subscribe to (e.g., @seth or seth)'
|
|
25
|
+
},
|
|
26
|
+
plan_id: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'Specific plan ID (optional - defaults to creator\'s default plan)'
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
required: ['creator_handle']
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
async function handler(args) {
|
|
36
|
+
const { creator_handle, plan_id } = args;
|
|
37
|
+
|
|
38
|
+
if (!config.isInitialized()) {
|
|
39
|
+
return {
|
|
40
|
+
display: 'Run `vibe init` first to set your identity.'
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const handle = config.getHandle();
|
|
45
|
+
const token = config.getToken();
|
|
46
|
+
const apiUrl = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
|
|
47
|
+
|
|
48
|
+
// Normalize creator handle
|
|
49
|
+
const normalizedCreator = creator_handle.replace(/^@/, '').toLowerCase();
|
|
50
|
+
|
|
51
|
+
if (normalizedCreator === handle.toLowerCase()) {
|
|
52
|
+
return {
|
|
53
|
+
display: '❌ You cannot subscribe to yourself'
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
let targetPlanId = plan_id;
|
|
59
|
+
|
|
60
|
+
// First, check available plans if no plan_id specified
|
|
61
|
+
if (!targetPlanId) {
|
|
62
|
+
const plansResponse = await fetch(
|
|
63
|
+
`${apiUrl}/api/subscriptions/plans?creator_handle=${encodeURIComponent(normalizedCreator)}`,
|
|
64
|
+
{
|
|
65
|
+
headers: token ? {
|
|
66
|
+
'Authorization': `Bearer ${token}`
|
|
67
|
+
} : {}
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const plansResult = await plansResponse.json();
|
|
72
|
+
|
|
73
|
+
if (!plansResponse.ok) {
|
|
74
|
+
return {
|
|
75
|
+
display: `❌ ${plansResult.error || 'Failed to fetch creator plans'}`
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!plansResult.plans || plansResult.plans.length === 0) {
|
|
80
|
+
return {
|
|
81
|
+
display: `❌ @${normalizedCreator} doesn't have any subscription plans yet`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Use default or first plan
|
|
86
|
+
const defaultPlan = plansResult.plans.find(p => p.is_default) || plansResult.plans[0];
|
|
87
|
+
targetPlanId = defaultPlan.id;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Subscribe to the plan
|
|
91
|
+
const response = await fetch(
|
|
92
|
+
`${apiUrl}/api/subscriptions/subscribe`,
|
|
93
|
+
{
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'Content-Type': 'application/json',
|
|
97
|
+
'Authorization': `Bearer ${token}`
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
plan_id: targetPlanId
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const result = await response.json();
|
|
106
|
+
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
let display = `❌ ${result.error || 'Failed to subscribe'}`;
|
|
109
|
+
if (result.action) {
|
|
110
|
+
display += `\n\nNext step: ${result.action}`;
|
|
111
|
+
}
|
|
112
|
+
return { display };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const periodEnd = new Date(result.subscription.period_end);
|
|
116
|
+
const formattedEnd = periodEnd.toLocaleDateString('en-US', {
|
|
117
|
+
month: 'short',
|
|
118
|
+
day: 'numeric',
|
|
119
|
+
year: 'numeric'
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
display: `
|
|
124
|
+
🎉 Subscribed to @${normalizedCreator}!
|
|
125
|
+
|
|
126
|
+
Plan: ${result.plan_name}
|
|
127
|
+
Price: ${result.price_display}
|
|
128
|
+
Period: ${result.billing_period}
|
|
129
|
+
Renews: ${formattedEnd}
|
|
130
|
+
|
|
131
|
+
You now have access to:
|
|
132
|
+
⭐ All subscriber-only sessions
|
|
133
|
+
⭐ Exclusive content
|
|
134
|
+
⭐ Direct creator support
|
|
135
|
+
|
|
136
|
+
To cancel: vibe subscriptions → cancel
|
|
137
|
+
`.trim(),
|
|
138
|
+
data: result
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
} catch (error) {
|
|
142
|
+
return {
|
|
143
|
+
display: `❌ Failed to subscribe: ${error.message}`
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = { definition, handler };
|