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
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 });
@@ -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 };