slashvibe-mcp 0.3.21 → 0.3.23

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 (235) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +280 -47
  3. package/auto-update.js +10 -15
  4. package/config.js +36 -31
  5. package/crypto.js +1 -6
  6. package/debug.js +12 -0
  7. package/discord.js +19 -19
  8. package/eslint.config.js +54 -0
  9. package/index.js +217 -207
  10. package/intelligence/index.js +2 -9
  11. package/intelligence/infer.js +10 -16
  12. package/intelligence/patterns.js +23 -18
  13. package/intelligence/proactive.js +16 -15
  14. package/intelligence/serendipity.js +57 -20
  15. package/memory.js +13 -8
  16. package/migrate-v2.js +72 -0
  17. package/notification-emitter.js +2 -2
  18. package/notify.js +39 -14
  19. package/package.json +28 -29
  20. package/post-install.js +141 -0
  21. package/presence.js +2 -2
  22. package/prompts.js +5 -9
  23. package/protocol/index.js +123 -87
  24. package/protocol/telegram-commands.js +36 -37
  25. package/store/api.js +358 -529
  26. package/store/local.js +9 -10
  27. package/store/profiles.js +48 -192
  28. package/store/reservations.js +2 -9
  29. package/store/skills.js +69 -71
  30. package/store/sqlite.js +355 -0
  31. package/test-skills-bootstrap.js +20 -0
  32. package/test-v2-integration.js +385 -0
  33. package/tools/_actions.js +48 -387
  34. package/tools/_connection-queue.js +45 -56
  35. package/tools/_discovery-enhanced.js +52 -57
  36. package/tools/_discovery.js +87 -185
  37. package/tools/{l2-status.js → _experimental/l2-status.js} +68 -70
  38. package/tools/{shipback.js → _experimental/shipback.js} +4 -3
  39. package/tools/_proactive-discovery.js +60 -73
  40. package/tools/_shared/index.js +41 -64
  41. package/tools/admin-inbox.js +10 -15
  42. package/tools/agents.js +1 -1
  43. package/tools/artifact-create.js +13 -23
  44. package/tools/artifact-view.js +4 -4
  45. package/tools/{_deprecated/back.js → back.js} +1 -1
  46. package/tools/bye.js +3 -5
  47. package/tools/consent.js +2 -2
  48. package/tools/context.js +9 -10
  49. package/tools/crossword.js +3 -2
  50. package/tools/discover.js +94 -356
  51. package/tools/dm.js +27 -86
  52. package/tools/doctor.js +12 -41
  53. package/tools/drawing.js +34 -20
  54. package/tools/echo.js +11 -11
  55. package/tools/feed.js +30 -58
  56. package/tools/follow.js +64 -187
  57. package/tools/{_deprecated/forget.js → forget.js} +4 -7
  58. package/tools/game.js +144 -48
  59. package/tools/handoff.js +6 -8
  60. package/tools/help.js +3 -3
  61. package/tools/idea.js +15 -27
  62. package/tools/inbox.js +121 -293
  63. package/tools/init.js +54 -151
  64. package/tools/invite.js +8 -21
  65. package/tools/migrate.js +27 -24
  66. package/tools/multiplayer-game.js +50 -40
  67. package/tools/{_deprecated/mute.js → mute.js} +4 -3
  68. package/tools/notifications.js +58 -48
  69. package/tools/observe.js +12 -15
  70. package/tools/onboarding.js +8 -11
  71. package/tools/open.js +13 -144
  72. package/tools/party-game.js +23 -12
  73. package/tools/patterns.js +2 -1
  74. package/tools/ping.js +5 -7
  75. package/tools/react.js +28 -30
  76. package/tools/{_deprecated/recall.js → recall.js} +5 -10
  77. package/tools/release.js +4 -2
  78. package/tools/{_deprecated/remember.js → remember.js} +4 -6
  79. package/tools/report.js +2 -2
  80. package/tools/request.js +6 -26
  81. package/tools/reserve.js +1 -1
  82. package/tools/session-fork.js +97 -0
  83. package/tools/session-save.js +109 -0
  84. package/tools/settings.js +30 -99
  85. package/tools/ship.js +74 -56
  86. package/tools/{_deprecated/skills-exchange.js → skills-exchange.js} +38 -39
  87. package/tools/social-inbox.js +22 -28
  88. package/tools/social-post.js +24 -27
  89. package/tools/solo-game.js +54 -46
  90. package/tools/start.js +14 -148
  91. package/tools/status.js +21 -68
  92. package/tools/submit.js +4 -2
  93. package/tools/suggest-tags.js +36 -33
  94. package/tools/summarize.js +19 -16
  95. package/tools/tag-suggestions.js +72 -73
  96. package/tools/test.js +1 -1
  97. package/tools/{_deprecated/tictactoe.js → tictactoe.js} +26 -26
  98. package/tools/token.js +4 -4
  99. package/tools/update.js +1 -2
  100. package/tools/watch.js +132 -112
  101. package/tools/who.js +20 -40
  102. package/tools/{_deprecated/wordassociation.js → wordassociation.js} +23 -20
  103. package/tools/workshop-buddy.js +52 -53
  104. package/tools/x-mentions.js +0 -1
  105. package/tools/x-reply.js +0 -1
  106. package/twitter.js +14 -20
  107. package/version.json +8 -10
  108. package/webhook-runner.js +132 -0
  109. package/auth-store.js +0 -148
  110. package/bridges/bridge-monitor.js +0 -388
  111. package/bridges/discord-bot.js +0 -431
  112. package/bridges/farcaster.js +0 -299
  113. package/bridges/telegram.js +0 -261
  114. package/bridges/webhook-health.js +0 -420
  115. package/bridges/webhook-server.js +0 -437
  116. package/bridges/whatsapp.js +0 -441
  117. package/bridges/x-webhook.js +0 -423
  118. package/games/arcade.js +0 -406
  119. package/games/chess.js +0 -451
  120. package/games/colorguess.js +0 -343
  121. package/games/crossword-words.js +0 -171
  122. package/games/crossword.js +0 -461
  123. package/games/drawing.js +0 -347
  124. package/games/gameroulette.js +0 -300
  125. package/games/gamerouter.js +0 -336
  126. package/games/gamestatus.js +0 -337
  127. package/games/guessnumber.js +0 -209
  128. package/games/hangman.js +0 -279
  129. package/games/memory.js +0 -338
  130. package/games/multiplayer-tictactoe.js +0 -389
  131. package/games/pixelart.js +0 -399
  132. package/games/quickduel.js +0 -354
  133. package/games/riddle.js +0 -371
  134. package/games/rockpaperscissors.js +0 -291
  135. package/games/snake.js +0 -406
  136. package/games/storybuilder.js +0 -343
  137. package/games/tictactoe.js +0 -345
  138. package/games/twentyquestions.js +0 -286
  139. package/games/twotruths.js +0 -207
  140. package/games/werewolf.js +0 -508
  141. package/games/wordassociation.js +0 -247
  142. package/games/wordchain.js +0 -135
  143. package/intelligence/interests.js +0 -369
  144. package/setup.js +0 -480
  145. package/smart-inbox.js +0 -276
  146. package/tools/_deprecated/auto-suggest-connections.js +0 -304
  147. package/tools/_deprecated/bootstrap-skills.js +0 -231
  148. package/tools/_deprecated/bridge-dashboard.js +0 -342
  149. package/tools/_deprecated/bridge-health.js +0 -400
  150. package/tools/_deprecated/bridge-live.js +0 -384
  151. package/tools/_deprecated/bridges.js +0 -383
  152. package/tools/_deprecated/colorguess.js +0 -281
  153. package/tools/_deprecated/discover-insights.js +0 -379
  154. package/tools/_deprecated/discover-momentum.js +0 -256
  155. package/tools/_deprecated/discovery-analytics.js +0 -345
  156. package/tools/_deprecated/discovery-auto-suggest.js +0 -275
  157. package/tools/_deprecated/discovery-bootstrap.js +0 -267
  158. package/tools/_deprecated/discovery-daily.js +0 -375
  159. package/tools/_deprecated/discovery-dashboard.js +0 -385
  160. package/tools/_deprecated/discovery-digest.js +0 -314
  161. package/tools/_deprecated/discovery-hub.js +0 -357
  162. package/tools/_deprecated/discovery-insights.js +0 -384
  163. package/tools/_deprecated/discovery-momentum.js +0 -281
  164. package/tools/_deprecated/discovery-monitor.js +0 -319
  165. package/tools/_deprecated/discovery-proactive.js +0 -300
  166. package/tools/_deprecated/draw.js +0 -317
  167. package/tools/_deprecated/farcaster.js +0 -307
  168. package/tools/_deprecated/games-catalog.js +0 -376
  169. package/tools/_deprecated/games.js +0 -313
  170. package/tools/_deprecated/guessnumber.js +0 -194
  171. package/tools/_deprecated/hangman.js +0 -129
  172. package/tools/_deprecated/multiplayer-tictactoe.js +0 -303
  173. package/tools/_deprecated/riddle.js +0 -240
  174. package/tools/_deprecated/run-bootstrap.js +0 -69
  175. package/tools/_deprecated/skills-analytics.js +0 -349
  176. package/tools/_deprecated/skills-bootstrap.js +0 -301
  177. package/tools/_deprecated/skills-dashboard.js +0 -268
  178. package/tools/_deprecated/skills.js +0 -380
  179. package/tools/_deprecated/smart-intro.js +0 -353
  180. package/tools/_deprecated/storybuilder.js +0 -331
  181. package/tools/_deprecated/telegram-bot.js +0 -183
  182. package/tools/_deprecated/telegram-setup.js +0 -214
  183. package/tools/_deprecated/twentyquestions.js +0 -143
  184. package/tools/_shared.js +0 -234
  185. package/tools/_work-context.js +0 -338
  186. package/tools/_work-context.manual-test.js +0 -199
  187. package/tools/_work-context.test.js +0 -260
  188. package/tools/activity.js +0 -220
  189. package/tools/agent-treasury.js +0 -288
  190. package/tools/analytics.js +0 -191
  191. package/tools/approve.js +0 -197
  192. package/tools/arcade.js +0 -173
  193. package/tools/artifacts-price.js +0 -107
  194. package/tools/ask-expert.js +0 -160
  195. package/tools/available.js +0 -120
  196. package/tools/become-expert.js +0 -150
  197. package/tools/broadcast.js +0 -325
  198. package/tools/chat.js +0 -202
  199. package/tools/collaborative-drawing.js +0 -286
  200. package/tools/connection-status.js +0 -178
  201. package/tools/earnings.js +0 -126
  202. package/tools/friends.js +0 -207
  203. package/tools/genesis.js +0 -233
  204. package/tools/gig-browse.js +0 -206
  205. package/tools/gig-complete.js +0 -144
  206. package/tools/health.js +0 -87
  207. package/tools/leaderboard.js +0 -117
  208. package/tools/lib/git-apply.js +0 -206
  209. package/tools/lib/git-bundle.js +0 -407
  210. package/tools/mint.js +0 -377
  211. package/tools/plan.js +0 -225
  212. package/tools/profile.js +0 -219
  213. package/tools/proof-of-work.js +0 -144
  214. package/tools/pulse.js +0 -218
  215. package/tools/reply.js +0 -166
  216. package/tools/reputation.js +0 -175
  217. package/tools/schedule.js +0 -367
  218. package/tools/search-messages.js +0 -123
  219. package/tools/session.js +0 -467
  220. package/tools/session_price.js +0 -128
  221. package/tools/smart-check.js +0 -201
  222. package/tools/social-processor.js +0 -445
  223. package/tools/streak.js +0 -147
  224. package/tools/stuck.js +0 -297
  225. package/tools/subscribe.js +0 -148
  226. package/tools/subscriptions.js +0 -134
  227. package/tools/tip.js +0 -193
  228. package/tools/wallet.js +0 -269
  229. package/tools/webhook-test.js +0 -388
  230. package/tools/withdraw.js +0 -145
  231. package/tools/work-summary.js +0 -96
  232. package/tools/workshop.js +0 -327
  233. /package/tools/{l2-bridge.js → _experimental/l2-bridge.js} +0 -0
  234. /package/tools/{l2.js → _experimental/l2.js} +0 -0
  235. /package/tools/{_deprecated/away.js → away.js} +0 -0
@@ -0,0 +1,97 @@
1
+ /**
2
+ * vibe session-fork — Fork someone else's session
3
+ *
4
+ * Creates a fork of an existing session, giving you a copy to build on.
5
+ * Supports 3 temporal destinations: session (replay), branch (code), or live (broadcast).
6
+ *
7
+ * Part of the core demo loop:
8
+ * Code with AI -> Session captured -> Replayable -> Discoverable -> Forkable -> Reputation accrues
9
+ *
10
+ * API: POST /api/sessions/fork
11
+ */
12
+
13
+ const config = require('../config');
14
+ const { requireInit } = require('./_shared');
15
+
16
+ const definition = {
17
+ name: 'vibe_session_fork',
18
+ description: 'Fork an existing session to build on it. Creates a copy you can extend.',
19
+ inputSchema: {
20
+ type: 'object',
21
+ properties: {
22
+ session_id: {
23
+ type: 'string',
24
+ description: 'Session ID to fork (e.g., ses_xxx)'
25
+ },
26
+ fork_to: {
27
+ type: 'string',
28
+ enum: ['session', 'branch', 'live'],
29
+ description: 'Fork destination: session (replay copy), branch (code copy), live (start broadcast from fork)'
30
+ }
31
+ },
32
+ required: ['session_id']
33
+ }
34
+ };
35
+
36
+ async function handler(args) {
37
+ const initCheck = requireInit();
38
+ if (initCheck) return initCheck;
39
+
40
+ const { session_id, fork_to } = args;
41
+ const myHandle = config.getHandle();
42
+ const apiUrl = config.getApiUrl();
43
+
44
+ try {
45
+ const body = {
46
+ parentSessionId: session_id,
47
+ forkerHandle: myHandle,
48
+ forkTo: fork_to || 'session'
49
+ };
50
+
51
+ const response = await fetch(`${apiUrl}/api/sessions/fork`, {
52
+ method: 'POST',
53
+ headers: { 'Content-Type': 'application/json' },
54
+ body: JSON.stringify(body)
55
+ });
56
+
57
+ const data = await response.json();
58
+
59
+ if (!response.ok || data.error) {
60
+ return {
61
+ display: `## Fork Failed\n\n${data.error || `HTTP ${response.status}`}\n\nMake sure the session ID is valid. Try \`vibe feed\` to browse sessions.`
62
+ };
63
+ }
64
+
65
+ const forkId = data.forkId || data.id || 'unknown';
66
+ const destination = data.destination || fork_to || 'session';
67
+ const forkUrl = data.url || `https://slashvibe.dev/sessions/${forkId}`;
68
+
69
+ let display = `## Session Forked\n\n`;
70
+ display += `**Forked from:** \`${session_id}\`\n`;
71
+ display += `**Fork ID:** \`${forkId}\`\n`;
72
+ display += `**Destination:** ${destination}\n`;
73
+ display += `**URL:** ${forkUrl}\n\n`;
74
+
75
+ switch (destination) {
76
+ case 'session':
77
+ display += `Your fork is saved as a new session. You can replay or extend it.`;
78
+ break;
79
+ case 'branch':
80
+ display += `Code copy created. Start building on top of the original session.`;
81
+ break;
82
+ case 'live':
83
+ display += `Live broadcast started from fork. Others can watch you build on this.`;
84
+ break;
85
+ }
86
+
87
+ display += `\n\nThe original author gets credit — fork lineage is tracked.`;
88
+
89
+ return { display };
90
+ } catch (error) {
91
+ return {
92
+ display: `## Fork Error\n\n${error.message}\n\nRun \`vibe doctor\` to diagnose connectivity issues.`
93
+ };
94
+ }
95
+ }
96
+
97
+ module.exports = { definition, handler };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * vibe session-save — Save your current session
3
+ *
4
+ * Persists a coding session to the platform so it becomes
5
+ * replayable, discoverable, and forkable.
6
+ *
7
+ * Part of the core demo loop:
8
+ * Code with AI -> Session captured -> Replayable -> Discoverable -> Forkable -> Reputation accrues
9
+ *
10
+ * API: POST /api/sessions
11
+ */
12
+
13
+ const config = require('../config');
14
+ const { requireInit } = require('./_shared');
15
+
16
+ const definition = {
17
+ name: 'vibe_session_save',
18
+ description: 'Save your current coding session. Makes it replayable, discoverable, and forkable on slashvibe.dev.',
19
+ inputSchema: {
20
+ type: 'object',
21
+ properties: {
22
+ title: {
23
+ type: 'string',
24
+ description: 'Session title (e.g., "Building auth with OAuth")'
25
+ },
26
+ description: {
27
+ type: 'string',
28
+ description: 'Brief description of what happened in the session'
29
+ },
30
+ from_broadcast: {
31
+ type: 'string',
32
+ description: 'Room ID if saving from a live broadcast'
33
+ },
34
+ visibility: {
35
+ type: 'string',
36
+ enum: ['public', 'unlisted', 'private'],
37
+ description: 'Session visibility (default: public)'
38
+ }
39
+ },
40
+ required: ['title']
41
+ }
42
+ };
43
+
44
+ async function handler(args) {
45
+ const initCheck = requireInit();
46
+ if (initCheck) return initCheck;
47
+
48
+ const { title, description, from_broadcast, visibility } = args;
49
+ const myHandle = config.getHandle();
50
+ const apiUrl = config.getApiUrl();
51
+
52
+ try {
53
+ const body = {
54
+ author_handle: myHandle,
55
+ title,
56
+ visibility: visibility || 'public'
57
+ };
58
+
59
+ if (description) {
60
+ body.description = description;
61
+ }
62
+
63
+ if (from_broadcast) {
64
+ body.fromBroadcast = from_broadcast;
65
+ }
66
+
67
+ const response = await fetch(`${apiUrl}/api/sessions`, {
68
+ method: 'POST',
69
+ headers: { 'Content-Type': 'application/json' },
70
+ body: JSON.stringify(body)
71
+ });
72
+
73
+ const data = await response.json();
74
+
75
+ if (!response.ok || data.error) {
76
+ return {
77
+ display: `## Session Save Failed\n\n${data.error || `HTTP ${response.status}`}\n\nCheck \`vibe doctor\` if this persists.`
78
+ };
79
+ }
80
+
81
+ const session = data.session || data;
82
+ const sessionId = session.id || session.sessionId || 'unknown';
83
+ const sessionUrl = session.url || `https://slashvibe.dev/sessions/${sessionId}`;
84
+
85
+ let display = `## Session Saved\n\n`;
86
+ display += `**${title}**\n`;
87
+ if (description) {
88
+ display += `${description}\n`;
89
+ }
90
+ display += `\n`;
91
+ display += `**ID:** \`${sessionId}\`\n`;
92
+ display += `**URL:** ${sessionUrl}\n`;
93
+ display += `**Visibility:** ${visibility || 'public'}\n`;
94
+ if (from_broadcast) {
95
+ display += `**From broadcast:** \`${from_broadcast}\`\n`;
96
+ }
97
+ display += `\n`;
98
+ display += `Others can now replay, discover, and fork this session.\n`;
99
+ display += `Share: \`${sessionUrl}\``;
100
+
101
+ return { display };
102
+ } catch (error) {
103
+ return {
104
+ display: `## Session Save Error\n\n${error.message}\n\nMake sure you're connected to the internet. Run \`vibe doctor\` to diagnose.`
105
+ };
106
+ }
107
+ }
108
+
109
+ module.exports = { definition, handler };
package/tools/settings.js CHANGED
@@ -6,15 +6,14 @@
6
6
  * - guided: on | off (dashboard mode)
7
7
  * - github_activity: on | off (show GitHub shipping status)
8
8
  * - github_activity_privacy: full | status_only | off
9
- * - mute: 1h | 2h | 4h | forever | off (merged from vibe_mute)
10
- * - auto_context: on | off (ambient work context gathering)
11
9
  */
12
10
 
13
11
  const config = require('../config');
14
12
 
15
13
  const definition = {
16
14
  name: 'vibe_settings',
17
- description: 'Configure /vibe preferences. Set notification level, toggle guided mode, enable GitHub activity signals, or mute alerts.',
15
+ description:
16
+ 'Configure /vibe preferences. Set notification level, toggle guided mode, or enable GitHub activity signals.',
18
17
  inputSchema: {
19
18
  type: 'object',
20
19
  properties: {
@@ -35,14 +34,6 @@ const definition = {
35
34
  type: 'string',
36
35
  enum: ['full', 'status_only', 'off'],
37
36
  description: 'Privacy level: full (repos/commits), status_only (just badge), off'
38
- },
39
- mute: {
40
- type: 'string',
41
- description: 'Mute presence alerts: "1h", "2h", "4h", "forever", or "off" to unmute'
42
- },
43
- auto_context: {
44
- type: 'boolean',
45
- description: 'Enable/disable automatic work context gathering (git branch, recent commits shown in presence)'
46
37
  }
47
38
  }
48
39
  }
@@ -75,68 +66,12 @@ async function handler(args) {
75
66
  changes.push('github activity privacy → **' + args.github_activity_privacy + '**');
76
67
  }
77
68
 
78
- // Handle mute setting (merged from vibe_mute)
79
- if (args.mute) {
80
- const duration = args.mute;
81
-
82
- // UNMUTE
83
- if (duration === 'off' || duration === 'unmute') {
84
- config.set('mutedUntil', null);
85
- config.set('focusMode', false);
86
- changes.push('alerts → **unmuted**');
87
- }
88
- // MUTE FOREVER
89
- else if (duration === 'forever' || duration === 'disable') {
90
- config.set('presenceAgentEnabled', false);
91
- config.set('mutedUntil', null);
92
- changes.push('alerts → **disabled**');
93
- }
94
- // PARSE DURATION
95
- else {
96
- let ms = 0;
97
- const match = duration.match(/^(\d+)(h|m|min|hour|hours|minutes)$/);
98
-
99
- if (match) {
100
- const amount = parseInt(match[1]);
101
- const unit = match[2];
102
-
103
- if (unit === 'h' || unit === 'hour' || unit === 'hours') {
104
- ms = amount * 60 * 60 * 1000;
105
- } else if (unit === 'm' || unit === 'min' || unit === 'minutes') {
106
- ms = amount * 60 * 1000;
107
- }
108
- } else {
109
- // Default to 1 hour if can't parse
110
- ms = 60 * 60 * 1000;
111
- }
112
-
113
- const mutedUntil = Date.now() + ms;
114
- config.set('mutedUntil', mutedUntil);
115
-
116
- const until = new Date(mutedUntil).toLocaleTimeString();
117
- const durationText = ms >= 3600000
118
- ? `${Math.floor(ms / 3600000)} hour${Math.floor(ms / 3600000) > 1 ? 's' : ''}`
119
- : `${Math.floor(ms / 60000)} minutes`;
120
-
121
- changes.push(`alerts → **muted for ${durationText}** (until ${until})`);
122
- }
123
- }
124
-
125
- // Update auto context if provided
126
- if (args.auto_context !== undefined) {
127
- config.set('autoContext', args.auto_context);
128
- changes.push('auto context → **' + (args.auto_context ? 'on' : 'off') + '**');
129
- }
130
-
131
69
  // If no args, show current settings
132
70
  if (changes.length === 0) {
133
71
  const notifications = config.getNotifications();
134
72
  const guided = config.getGuidedMode();
135
73
  const githubActivity = config.getGithubActivityEnabled();
136
74
  const githubPrivacy = config.getGithubActivityPrivacy();
137
- const mutedUntil = config.get('mutedUntil');
138
- const presenceEnabled = config.get('presenceAgentEnabled') !== false;
139
- const autoContext = config.get('autoContext', true); // Default to true
140
75
 
141
76
  const notifyDesc = {
142
77
  all: 'All notifications (messages, mentions, presence)',
@@ -150,49 +85,45 @@ async function handler(args) {
150
85
  off: 'Disabled'
151
86
  };
152
87
 
153
- // Determine mute status
154
- let muteStatus = 'off';
155
- let muteDesc = 'Alerts enabled';
156
- if (!presenceEnabled) {
157
- muteStatus = 'disabled';
158
- muteDesc = 'Presence alerts permanently disabled';
159
- } else if (mutedUntil) {
160
- const remaining = mutedUntil - Date.now();
161
- if (remaining > 0) {
162
- const mins = Math.floor(remaining / 60000);
163
- const hours = Math.floor(mins / 60);
164
- muteStatus = hours > 0 ? `${hours}h ${mins % 60}m` : `${mins}m`;
165
- muteDesc = `Muted until ${new Date(mutedUntil).toLocaleTimeString()}`;
166
- }
167
- }
168
-
169
88
  return {
170
- display: '## /vibe Settings\n\n' +
171
- '**Notifications:** ' + notifications + '\n' +
172
- '_' + notifyDesc[notifications] + '_\n\n' +
173
- '**Guided Mode:** ' + (guided ? 'on' : 'off') + '\n' +
174
- '_' + (guided ? 'Shows dashboard menus' : 'Freeform mode') + '_\n\n' +
175
- '**Alert Mute:** ' + muteStatus + '\n' +
176
- '_' + muteDesc + '_\n\n' +
177
- '**GitHub Activity:** ' + (githubActivity ? 'on' : 'off') + '\n' +
178
- '_' + (githubActivity ? 'Shows shipping status from your GitHub commits' : 'Not sharing GitHub activity') + '_\n\n' +
89
+ display:
90
+ '## /vibe Settings\n\n' +
91
+ '**Notifications:** ' +
92
+ notifications +
93
+ '\n' +
94
+ '_' +
95
+ notifyDesc[notifications] +
96
+ '_\n\n' +
97
+ '**Guided Mode:** ' +
98
+ (guided ? 'on' : 'off') +
99
+ '\n' +
100
+ '_' +
101
+ (guided ? 'Shows dashboard menus' : 'Freeform mode') +
102
+ '_\n\n' +
103
+ '**GitHub Activity:** ' +
104
+ (githubActivity ? 'on' : 'off') +
105
+ '\n' +
106
+ '_' +
107
+ (githubActivity ? 'Shows shipping status from your GitHub commits' : 'Not sharing GitHub activity') +
108
+ '_\n\n' +
179
109
  (githubActivity ? '**GitHub Privacy:** ' + githubPrivacy + '\n_' + privacyDesc[githubPrivacy] + '_\n\n' : '') +
180
- '**Auto Context:** ' + (autoContext ? 'on' : 'off') + '\n' +
181
- '_' + (autoContext ? 'Git branch & work summary shared in presence' : 'Work context not auto-shared') + '_\n\n' +
182
110
  '---\n\n' +
183
111
  '**Change settings:**\n' +
184
112
  '• "set notifications to mentions" — less noisy\n' +
185
113
  '• "turn off guided mode" — no menus\n' +
186
- '• "mute for 2h" — silence alerts temporarily\n' +
187
114
  '• "enable github activity" — show when you\'re shipping\n' +
188
- '• "set github privacy to status_only" — just badge, no details\n' +
189
- '• "disable auto context" — stop sharing git branch/work'
115
+ '• "set github privacy to status_only" — just badge, no details'
190
116
  };
191
117
  }
192
118
 
193
119
  return {
194
- display: '## Settings Updated\n\n' +
195
- changes.map(function(c) { return ' ' + c; }).join('\n') +
120
+ display:
121
+ '## Settings Updated\n\n' +
122
+ changes
123
+ .map(function (c) {
124
+ return '✓ ' + c;
125
+ })
126
+ .join('\n') +
196
127
  '\n\n_Changes take effect immediately._'
197
128
  };
198
129
  }
package/tools/ship.js CHANGED
@@ -1,29 +1,42 @@
1
1
  /**
2
- * vibe ship — Announce what you just shipped
2
+ * vibe ship — Share with the community
3
3
  *
4
- * Share your wins with the community and update your profile.
5
- * Tracks your shipping history for better discovery matches.
4
+ * Unified creative entry point with type parameter:
5
+ * - type: "ship" (default) Announce what you shipped
6
+ * - type: "idea" — Post a raw idea for others to riff on
7
+ * - type: "request" — Post a build request / wish
6
8
  *
7
- * Usage:
8
- * - ship "Built a new feature for my AI chat app"
9
- * - ship "Deployed my portfolio website"
10
- * - ship "Published blog post about React patterns"
9
+ * Absorbs former vibe_idea and vibe_request tools.
11
10
  */
12
11
 
13
12
  const config = require('../config');
14
13
  const userProfiles = require('../store/profiles');
15
14
  const patterns = require('../intelligence/patterns');
16
- const { requireInit, formatTimeAgo } = require('./_shared');
15
+ const { requireInit, normalizeHandle, formatTimeAgo, debug } = require('./_shared');
16
+
17
+ // Delegate handlers for absorbed tools
18
+ const ideaTool = require('./idea');
19
+ const requestTool = require('./request');
17
20
 
18
21
  const definition = {
19
22
  name: 'vibe_ship',
20
- description: 'Announce something you just shipped to the community board and update your profile.',
23
+ description:
24
+ 'Share with the community. type="ship" (default): announce what you shipped. type="idea": post an idea for others to riff on. type="request": post a build request.',
21
25
  inputSchema: {
22
26
  type: 'object',
23
27
  properties: {
28
+ type: {
29
+ type: 'string',
30
+ enum: ['ship', 'idea', 'request'],
31
+ description: 'What to share: ship (default), idea, or request'
32
+ },
24
33
  what: {
25
34
  type: 'string',
26
- description: 'What you shipped (brief description)'
35
+ description: 'What you shipped (for type=ship)'
36
+ },
37
+ content: {
38
+ type: 'string',
39
+ description: 'Content for idea or request (for type=idea/request)'
27
40
  },
28
41
  url: {
29
42
  type: 'string',
@@ -41,9 +54,20 @@ const definition = {
41
54
  type: 'array',
42
55
  items: { type: 'string' },
43
56
  description: 'Tags for discovery (e.g., ["ai", "mcp", "tools"])'
57
+ },
58
+ riff_on: {
59
+ type: 'string',
60
+ description: 'Handle to riff on (for type=idea)'
61
+ },
62
+ claim: {
63
+ type: 'string',
64
+ description: 'Request ID to claim (for type=request)'
65
+ },
66
+ bounty: {
67
+ type: 'string',
68
+ description: "What you're offering for a request (for type=request)"
44
69
  }
45
- },
46
- required: ['what']
70
+ }
47
71
  }
48
72
  };
49
73
 
@@ -51,8 +75,18 @@ async function handler(args) {
51
75
  const initCheck = requireInit();
52
76
  if (initCheck) return initCheck;
53
77
 
78
+ // Route to absorbed tools by type
79
+ const type = args.type || 'ship';
80
+ if (type === 'idea') {
81
+ return ideaTool.handler(args);
82
+ }
83
+ if (type === 'request') {
84
+ return requestTool.handler(args);
85
+ }
86
+
87
+ // Default: ship
54
88
  if (!args.what) {
55
- return { error: 'Please tell us what you shipped: ship "Built a new feature"' };
89
+ return { display: 'Please tell us what you shipped: ship "Built a new feature"' };
56
90
  }
57
91
 
58
92
  const myHandle = config.getHandle();
@@ -70,7 +104,7 @@ async function handler(args) {
70
104
  metaParts.push(`🔗 ${args.url}`);
71
105
  }
72
106
  if (args.inspired_by) {
73
- const inspiree = args.inspired_by.replace('@', '').toLowerCase();
107
+ const inspiree = normalizeHandle(args.inspired_by);
74
108
  metaParts.push(`✨ inspired by @${inspiree}`);
75
109
  }
76
110
  if (args.for_request) {
@@ -84,22 +118,16 @@ async function handler(args) {
84
118
  // Build tags with attribution
85
119
  const tags = args.tags || [];
86
120
  if (args.inspired_by) {
87
- tags.push(`inspired:${args.inspired_by.replace('@', '')}`);
121
+ tags.push(`inspired:${normalizeHandle(args.inspired_by)}`);
88
122
  }
89
123
  if (args.for_request) {
90
124
  tags.push(`fulfills:${args.for_request}`);
91
125
  }
92
126
 
93
- // Post to board (with auth token)
94
- const authToken = config.getAuthToken();
95
- const headers = { 'Content-Type': 'application/json' };
96
- if (authToken) {
97
- headers['Authorization'] = `Bearer ${authToken}`;
98
- }
99
-
127
+ // Post to board
100
128
  const response = await fetch(`${apiUrl}/api/board`, {
101
129
  method: 'POST',
102
- headers,
130
+ headers: { 'Content-Type': 'application/json' },
103
131
  body: JSON.stringify({
104
132
  author: myHandle,
105
133
  content,
@@ -120,30 +148,17 @@ async function handler(args) {
120
148
  patterns.logInspiredBy(args.inspired_by);
121
149
  }
122
150
 
123
- let display = `🚀 **Shipped!**\n\n${args.what}`;
151
+ // Push ship event to subscribed agent gateways
152
+ const { pushToAgents } = require('../notify');
153
+ pushToAgents('ship', { author: myHandle, what: args.what, url: args.url, tags }).catch(() => {});
154
+
155
+ let display = `🚀 shipped\n\n${args.what}`;
124
156
 
125
157
  if (args.url) {
126
- display += `\n🔗 ${args.url}`;
158
+ display += `\n${args.url}`;
127
159
  }
128
160
  if (args.inspired_by) {
129
- display += `\n✨ _via @${args.inspired_by.replace('@', '')}_`;
130
- }
131
-
132
- // Add share URL for viral distribution
133
- if (data.shareUrl) {
134
- display += `\n\n**📣 Share your ship:**\n${data.shareUrl}`;
135
-
136
- // Use the twitterShareUrl from API response (tracked for K-factor)
137
- if (data.twitterShareUrl) {
138
- display += `\n\n**🐦 Share on Twitter** (helps others discover /vibe!)`;
139
- display += `\n${data.twitterShareUrl}`;
140
- } else {
141
- // Fallback to inline URL if API didn't return one
142
- const shareText = encodeURIComponent(`🚀 Just shipped: ${args.what.substring(0, 80)}\n\nBuilding with /vibe`);
143
- const encodedUrl = encodeURIComponent(data.shareUrl);
144
- display += `\n\n**Quick share:**`;
145
- display += `\n• Twitter: https://twitter.com/intent/tweet?text=${shareText}&url=${encodedUrl}&hashtags=buildinpublic,vibecoders`;
146
- }
161
+ display += `\n_via @${normalizeHandle(args.inspired_by)}_`;
147
162
  }
148
163
 
149
164
  display += '\n';
@@ -151,11 +166,13 @@ async function handler(args) {
151
166
  // Quiet awareness of similar builders
152
167
  const suggestions = await findSimilarShippers(myHandle, args.what);
153
168
  if (suggestions.length > 0) {
154
- display += `\n_similar builders: @${suggestions.slice(0, 2).map(s => s.handle).join(', @')}_`;
169
+ display += `\n_similar: @${suggestions
170
+ .slice(0, 2)
171
+ .map(s => s.handle)
172
+ .join(', @')}_`;
155
173
  }
156
174
 
157
175
  return { display };
158
-
159
176
  } catch (error) {
160
177
  return { display: `## Ship Error\n\n${error.message}` };
161
178
  }
@@ -165,7 +182,10 @@ async function handler(args) {
165
182
  async function findSimilarShippers(myHandle, whatIShipped) {
166
183
  try {
167
184
  const allProfiles = await userProfiles.getAllProfiles();
168
- const myWords = whatIShipped.toLowerCase().split(/\s+/).filter(w => w.length > 3);
185
+ const myWords = whatIShipped
186
+ .toLowerCase()
187
+ .split(/\s+/)
188
+ .filter(w => w.length > 3);
169
189
  const suggestions = [];
170
190
 
171
191
  for (const profile of allProfiles) {
@@ -175,7 +195,7 @@ async function findSimilarShippers(myHandle, whatIShipped) {
175
195
  for (const ship of profile.ships) {
176
196
  const shipWords = ship.what.toLowerCase().split(/\s+/);
177
197
  const overlap = myWords.filter(w => shipWords.includes(w));
178
-
198
+
179
199
  if (overlap.length > 0) {
180
200
  suggestions.push({
181
201
  handle: profile.handle,
@@ -189,17 +209,15 @@ async function findSimilarShippers(myHandle, whatIShipped) {
189
209
  }
190
210
 
191
211
  // Sort by overlap and recency
192
- return suggestions
193
- .sort((a, b) => {
194
- const overlapDiff = b.overlap - a.overlap;
195
- if (overlapDiff !== 0) return overlapDiff;
196
- return b.timestamp - a.timestamp;
197
- });
198
-
212
+ return suggestions.sort((a, b) => {
213
+ const overlapDiff = b.overlap - a.overlap;
214
+ if (overlapDiff !== 0) return overlapDiff;
215
+ return b.timestamp - a.timestamp;
216
+ });
199
217
  } catch (error) {
200
- console.warn('Error finding similar shippers:', error);
218
+ debug('ship', 'Error finding similar shippers:', error);
201
219
  return [];
202
220
  }
203
221
  }
204
222
 
205
- module.exports = { definition, handler };
223
+ module.exports = { definition, handler };