slashvibe-mcp 0.2.8 → 0.3.13
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 +41 -58
- 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 +77 -53
- 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 +18 -6
- package/prompts.js +1 -1
- package/protocol/index.js +73 -0
- package/setup.js +402 -0
- package/store/api.js +436 -211
- 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 +286 -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/help.js +3 -3
- package/tools/idea.js +9 -2
- package/tools/inbox.js +289 -105
- package/tools/init.js +106 -27
- package/tools/invite.js +15 -4
- 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 +420 -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/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/ship.js
CHANGED
|
@@ -90,10 +90,16 @@ async function handler(args) {
|
|
|
90
90
|
tags.push(`fulfills:${args.for_request}`);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
// Post to board
|
|
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
|
+
|
|
94
100
|
const response = await fetch(`${apiUrl}/api/board`, {
|
|
95
101
|
method: 'POST',
|
|
96
|
-
headers
|
|
102
|
+
headers,
|
|
97
103
|
body: JSON.stringify({
|
|
98
104
|
author: myHandle,
|
|
99
105
|
content,
|
|
@@ -114,21 +120,38 @@ async function handler(args) {
|
|
|
114
120
|
patterns.logInspiredBy(args.inspired_by);
|
|
115
121
|
}
|
|
116
122
|
|
|
117
|
-
let display = `🚀
|
|
123
|
+
let display = `🚀 **Shipped!**\n\n${args.what}`;
|
|
118
124
|
|
|
119
125
|
if (args.url) {
|
|
120
|
-
display += `\n${args.url}`;
|
|
126
|
+
display += `\n🔗 ${args.url}`;
|
|
121
127
|
}
|
|
122
128
|
if (args.inspired_by) {
|
|
123
|
-
display += `\
|
|
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
|
+
}
|
|
124
147
|
}
|
|
125
148
|
|
|
126
149
|
display += '\n';
|
|
127
|
-
|
|
150
|
+
|
|
128
151
|
// Quiet awareness of similar builders
|
|
129
152
|
const suggestions = await findSimilarShippers(myHandle, args.what);
|
|
130
153
|
if (suggestions.length > 0) {
|
|
131
|
-
display += `\n_similar: @${suggestions.slice(0, 2).map(s => s.handle).join(', @')}_`;
|
|
154
|
+
display += `\n_similar builders: @${suggestions.slice(0, 2).map(s => s.handle).join(', @')}_`;
|
|
132
155
|
}
|
|
133
156
|
|
|
134
157
|
return { display };
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Inbox Check Tool
|
|
3
|
+
*
|
|
4
|
+
* MCP tool for intelligent inbox checking at natural breaks.
|
|
5
|
+
* Designed to be called by Claude Code after commits, tests, builds, or natural pauses.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* vibe check - Explicit check now
|
|
9
|
+
* vibe check --after-commit - Check after a commit
|
|
10
|
+
* vibe check --after-test - Check after test run
|
|
11
|
+
* vibe check --after-build - Check after build
|
|
12
|
+
* vibe check --natural - Check at natural break
|
|
13
|
+
* vibe check --stats - Show check statistics
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const smartInbox = require('../smart-inbox');
|
|
17
|
+
const config = require('../config');
|
|
18
|
+
|
|
19
|
+
const TOOL_NAME = 'vibe_smart_check';
|
|
20
|
+
|
|
21
|
+
const toolDefinition = {
|
|
22
|
+
name: TOOL_NAME,
|
|
23
|
+
description: `Smart inbox check at natural breaks. Call this after commits, tests, builds, or between tasks. Returns unread messages and notifications. Debounced to avoid spam.
|
|
24
|
+
|
|
25
|
+
Example triggers:
|
|
26
|
+
- After git commit: trigger="commit", context={message: "feat: add auth"}
|
|
27
|
+
- After test: trigger="test_pass" or trigger="test_fail"
|
|
28
|
+
- After build: trigger="build_complete" or trigger="build_fail"
|
|
29
|
+
- Natural break: trigger="natural" (between tasks, after file saves)
|
|
30
|
+
- Explicit: trigger="explicit" or omit (check now regardless of timing)
|
|
31
|
+
|
|
32
|
+
The tool debounces checks to avoid notification spam (min 1 minute between checks for same trigger type).`,
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
trigger: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: 'What triggered this check',
|
|
39
|
+
enum: ['explicit', 'commit', 'test_pass', 'test_fail', 'build_complete', 'build_fail', 'natural'],
|
|
40
|
+
default: 'explicit',
|
|
41
|
+
},
|
|
42
|
+
context: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
description: 'Optional context about the trigger (commit message, test output, etc.)',
|
|
45
|
+
properties: {
|
|
46
|
+
message: { type: 'string', description: 'Commit message or summary' },
|
|
47
|
+
branch: { type: 'string', description: 'Git branch name' },
|
|
48
|
+
output: { type: 'string', description: 'Test/build output summary' },
|
|
49
|
+
file: { type: 'string', description: 'File being worked on' },
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
stats: {
|
|
53
|
+
type: 'boolean',
|
|
54
|
+
description: 'If true, return check statistics instead of checking inbox',
|
|
55
|
+
default: false,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
required: [],
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
async function handler({ trigger = 'explicit', context = {}, stats = false }) {
|
|
63
|
+
// Check initialization
|
|
64
|
+
if (!config.isInitialized()) {
|
|
65
|
+
return {
|
|
66
|
+
content: [{
|
|
67
|
+
type: 'text',
|
|
68
|
+
text: '❌ Not initialized. Run `vibe init` first.',
|
|
69
|
+
}],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Return stats if requested
|
|
74
|
+
if (stats) {
|
|
75
|
+
const checkStats = smartInbox.getStats();
|
|
76
|
+
const lines = [
|
|
77
|
+
'📊 **Smart Check Statistics**',
|
|
78
|
+
'',
|
|
79
|
+
`Last check: ${checkStats.lastCheck || 'Never'}`,
|
|
80
|
+
`Last trigger: ${checkStats.lastTrigger || 'None'}`,
|
|
81
|
+
`Total checks: ${checkStats.checkCount}`,
|
|
82
|
+
`Time since last: ${checkStats.timeSinceLastCheck ? `${checkStats.timeSinceLastCheck}s ago` : 'N/A'}`,
|
|
83
|
+
'',
|
|
84
|
+
'**Today\'s triggers:**',
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const todayTriggers = checkStats.todayTriggers;
|
|
88
|
+
if (Object.keys(todayTriggers).length === 0) {
|
|
89
|
+
lines.push(' (none yet)');
|
|
90
|
+
} else {
|
|
91
|
+
for (const [trig, count] of Object.entries(todayTriggers)) {
|
|
92
|
+
lines.push(` ${trig}: ${count}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
content: [{
|
|
98
|
+
type: 'text',
|
|
99
|
+
text: lines.join('\n'),
|
|
100
|
+
}],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Map trigger strings to constants
|
|
105
|
+
const triggerMap = {
|
|
106
|
+
explicit: smartInbox.TRIGGERS.EXPLICIT,
|
|
107
|
+
commit: smartInbox.TRIGGERS.COMMIT,
|
|
108
|
+
test_pass: smartInbox.TRIGGERS.TEST_PASS,
|
|
109
|
+
test_fail: smartInbox.TRIGGERS.TEST_FAIL,
|
|
110
|
+
build_complete: smartInbox.TRIGGERS.BUILD_COMPLETE,
|
|
111
|
+
build_fail: smartInbox.TRIGGERS.BUILD_FAIL,
|
|
112
|
+
natural: smartInbox.TRIGGERS.NATURAL_BREAK,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const triggerType = triggerMap[trigger] || smartInbox.TRIGGERS.EXPLICIT;
|
|
116
|
+
|
|
117
|
+
// Perform the check
|
|
118
|
+
const result = await smartInbox.check(triggerType, context);
|
|
119
|
+
|
|
120
|
+
// Build response
|
|
121
|
+
if (!result.checked) {
|
|
122
|
+
// Check was debounced or skipped
|
|
123
|
+
const waitSec = Math.ceil((result.waitMs || 0) / 1000);
|
|
124
|
+
return {
|
|
125
|
+
content: [{
|
|
126
|
+
type: 'text',
|
|
127
|
+
text: `⏳ Skipped (${result.reason})${waitSec > 0 ? ` — try again in ${waitSec}s` : ''}`,
|
|
128
|
+
}],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Format the result
|
|
133
|
+
const lines = [];
|
|
134
|
+
const handle = config.getHandle();
|
|
135
|
+
|
|
136
|
+
// Show what triggered the check
|
|
137
|
+
const triggerEmoji = {
|
|
138
|
+
commit: '📝',
|
|
139
|
+
test_pass: '✅',
|
|
140
|
+
test_fail: '❌',
|
|
141
|
+
build_complete: '🏗️',
|
|
142
|
+
build_fail: '🔴',
|
|
143
|
+
natural_break: '☕',
|
|
144
|
+
explicit: '🔔',
|
|
145
|
+
session_start: '🚀',
|
|
146
|
+
session_end: '👋',
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
lines.push(`${triggerEmoji[result.trigger] || '📬'} **Inbox check** (${result.trigger})`);
|
|
150
|
+
lines.push('');
|
|
151
|
+
|
|
152
|
+
// Unread messages
|
|
153
|
+
if (result.unreadCount > 0) {
|
|
154
|
+
lines.push(`📨 **${result.unreadCount} unread message${result.unreadCount > 1 ? 's' : ''}**`);
|
|
155
|
+
if (result.notified) {
|
|
156
|
+
lines.push(' → Notification sent');
|
|
157
|
+
}
|
|
158
|
+
lines.push(` → Open with \`vibe inbox\``);
|
|
159
|
+
} else {
|
|
160
|
+
lines.push('📭 No unread messages');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// New users online
|
|
164
|
+
if (result.newOnline && result.newOnline.length > 0) {
|
|
165
|
+
lines.push('');
|
|
166
|
+
lines.push(`🟢 **Just came online:** @${result.newOnline.join(', @')}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// New ships
|
|
170
|
+
if (result.newShips && result.newShips.length > 0) {
|
|
171
|
+
lines.push('');
|
|
172
|
+
lines.push(`🚀 **New ships from connections:** @${result.newShips.join(', @')}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Show context if provided
|
|
176
|
+
if (context.message) {
|
|
177
|
+
lines.push('');
|
|
178
|
+
lines.push(`_Context: "${context.message.slice(0, 60)}${context.message.length > 60 ? '...' : ''}"_`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
content: [{
|
|
183
|
+
type: 'text',
|
|
184
|
+
text: lines.join('\n'),
|
|
185
|
+
}],
|
|
186
|
+
// Include structured data for programmatic use
|
|
187
|
+
_meta: {
|
|
188
|
+
unreadCount: result.unreadCount,
|
|
189
|
+
notified: result.notified,
|
|
190
|
+
newOnline: result.newOnline,
|
|
191
|
+
newShips: result.newShips,
|
|
192
|
+
trigger: result.trigger,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
name: TOOL_NAME,
|
|
199
|
+
definition: toolDefinition,
|
|
200
|
+
handler,
|
|
201
|
+
};
|
package/tools/start.js
CHANGED
|
@@ -18,6 +18,7 @@ const notify = require('../notify');
|
|
|
18
18
|
const patterns = require('../intelligence/patterns');
|
|
19
19
|
const { actions, formatActions } = require('./_actions');
|
|
20
20
|
const init = require('./init');
|
|
21
|
+
const { gatherWithTimeout } = require('./_work-context');
|
|
21
22
|
|
|
22
23
|
const REPO_DIR = path.join(process.env.HOME, '.vibe', 'vibe-repo');
|
|
23
24
|
|
|
@@ -191,7 +192,7 @@ async function handler(args) {
|
|
|
191
192
|
|
|
192
193
|
// Step 1: Check if properly authenticated with OAuth
|
|
193
194
|
// If not, redirect to init for GitHub auth flow (shows pre-auth banner + OAuth)
|
|
194
|
-
if (!config.
|
|
195
|
+
if (!config.hasOAuth()) {
|
|
195
196
|
return init.handler({
|
|
196
197
|
handle: args.handle,
|
|
197
198
|
one_liner: args.building
|
|
@@ -199,7 +200,7 @@ async function handler(args) {
|
|
|
199
200
|
}
|
|
200
201
|
|
|
201
202
|
// Step 2: User is authenticated - show dashboard
|
|
202
|
-
|
|
203
|
+
const myHandle = config.getHandle();
|
|
203
204
|
let threads = [];
|
|
204
205
|
let updateNotice = '';
|
|
205
206
|
|
|
@@ -211,6 +212,32 @@ async function handler(args) {
|
|
|
211
212
|
// Fetch version info early (non-blocking, cached)
|
|
212
213
|
const versionInfo = await getVersionInfo().catch(() => null);
|
|
213
214
|
|
|
215
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
216
|
+
// AMBIENT CONTEXT: Gather work context and auto-set presence
|
|
217
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
218
|
+
let workContext = null;
|
|
219
|
+
const autoContextEnabled = config.get('autoContext', true); // Opt-out via settings
|
|
220
|
+
|
|
221
|
+
if (autoContextEnabled) {
|
|
222
|
+
try {
|
|
223
|
+
// Gather context with timeout (won't block startup)
|
|
224
|
+
workContext = await gatherWithTimeout(2000);
|
|
225
|
+
|
|
226
|
+
// Auto-set presence so others see what we're working on (silent, non-blocking)
|
|
227
|
+
if (workContext?.suggestions?.brief) {
|
|
228
|
+
// Use store.heartbeat directly instead of importing context tool
|
|
229
|
+
// This avoids circular dependencies and is simpler
|
|
230
|
+
store.heartbeat(myHandle, config.getOneLiner(), {
|
|
231
|
+
note: workContext.suggestions.brief,
|
|
232
|
+
branch: workContext.git?.branch || null
|
|
233
|
+
}).catch(() => {}); // Silent fail - don't block
|
|
234
|
+
}
|
|
235
|
+
} catch (e) {
|
|
236
|
+
// Silent fail - context is nice-to-have, not required
|
|
237
|
+
workContext = null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
214
241
|
// Log session start for patterns
|
|
215
242
|
patterns.logSessionStart(myHandle);
|
|
216
243
|
|
|
@@ -225,8 +252,12 @@ async function handler(args) {
|
|
|
225
252
|
|
|
226
253
|
// Step 3: Check inbox + trigger notifications
|
|
227
254
|
let unreadCount = 0;
|
|
255
|
+
let inboxThreads = [];
|
|
228
256
|
try {
|
|
229
|
-
|
|
257
|
+
// Fetch full inbox (not just count) so we can include summaries
|
|
258
|
+
inboxThreads = await store.getInbox(myHandle);
|
|
259
|
+
unreadCount = inboxThreads.reduce((sum, t) => sum + (t.unread || 0), 0);
|
|
260
|
+
|
|
230
261
|
if (unreadCount > 0) {
|
|
231
262
|
// Check for messages needing desktop notification escalation
|
|
232
263
|
const rawInbox = await store.getRawInbox(myHandle).catch(() => []);
|
|
@@ -236,6 +267,24 @@ async function handler(args) {
|
|
|
236
267
|
}
|
|
237
268
|
} catch (e) {}
|
|
238
269
|
|
|
270
|
+
// Step 4: Get connection suggestions from API
|
|
271
|
+
let suggestions = [];
|
|
272
|
+
try {
|
|
273
|
+
const apiUrl = config.getApiUrl();
|
|
274
|
+
const suggestionsResponse = await fetch(`${apiUrl}/api/suggestions?user=${myHandle}&limit=3`, {
|
|
275
|
+
headers: { 'User-Agent': 'vibe-mcp-client' }
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
if (suggestionsResponse.ok) {
|
|
279
|
+
const data = await suggestionsResponse.json();
|
|
280
|
+
if (data.success && data.suggestions) {
|
|
281
|
+
suggestions = data.suggestions;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
} catch (e) {
|
|
285
|
+
// Silent fail - suggestions are nice-to-have
|
|
286
|
+
}
|
|
287
|
+
|
|
239
288
|
// Generate the ASCII welcome card (matches init.js format)
|
|
240
289
|
const welcomeCard = generateWelcomeCard({
|
|
241
290
|
handle: myHandle,
|
|
@@ -247,6 +296,46 @@ async function handler(args) {
|
|
|
247
296
|
// Build display with card + any additional info
|
|
248
297
|
let display = welcomeCard;
|
|
249
298
|
|
|
299
|
+
// Add who's online section (top 5 with what they're building)
|
|
300
|
+
if (others.length > 0) {
|
|
301
|
+
const top5 = others.slice(0, 5);
|
|
302
|
+
display += `\n\n**🟢 Online now:**`;
|
|
303
|
+
top5.forEach(u => {
|
|
304
|
+
const status = u.status ? ` (${u.status})` : '';
|
|
305
|
+
const building = u.one_liner || u.note || '';
|
|
306
|
+
const truncated = building.length > 40 ? building.slice(0, 40) + '...' : building;
|
|
307
|
+
display += `\n• @${u.handle}${status}${truncated ? ' — ' + truncated : ''}`;
|
|
308
|
+
});
|
|
309
|
+
if (others.length > 5) {
|
|
310
|
+
display += `\n• _+${others.length - 5} more..._`;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Add unread messages section (if any)
|
|
315
|
+
if (unreadCount > 0) {
|
|
316
|
+
const unreadSenders = inboxThreads.filter(t => t.unread > 0);
|
|
317
|
+
display += `\n\n**📬 Unread (${unreadCount}):**`;
|
|
318
|
+
unreadSenders.slice(0, 3).forEach(t => {
|
|
319
|
+
const preview = t.lastMessage ? t.lastMessage.slice(0, 50) : '';
|
|
320
|
+
const truncated = preview.length > 50 ? preview + '...' : preview;
|
|
321
|
+
display += `\n• @${t.handle} (${t.unread}) — "${truncated}"`;
|
|
322
|
+
});
|
|
323
|
+
if (unreadSenders.length > 3) {
|
|
324
|
+
display += `\n• _+${unreadSenders.length - 3} more threads..._`;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Add connection suggestions (if any)
|
|
329
|
+
if (suggestions.length > 0) {
|
|
330
|
+
display += `\n\n**🤝 Suggested connections:**`;
|
|
331
|
+
suggestions.slice(0, 3).forEach(s => {
|
|
332
|
+
display += `\n• @${s.handle} — ${s.reason}`;
|
|
333
|
+
if (s.github_name) {
|
|
334
|
+
display += ` (${s.github_name})`;
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
250
339
|
// Add memory context for returning users
|
|
251
340
|
if (threads.length > 0) {
|
|
252
341
|
const recentThreads = threads.slice(0, 3);
|
|
@@ -259,17 +348,48 @@ async function handler(args) {
|
|
|
259
348
|
display += updateNotice;
|
|
260
349
|
}
|
|
261
350
|
|
|
262
|
-
// Step 6:
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
351
|
+
// Step 6: Show rotating tips about features
|
|
352
|
+
const tips = [
|
|
353
|
+
'💡 **Tip:** Say "vibe stuck" when you need help — others can jump in and assist.',
|
|
354
|
+
'💡 **Tip:** Use "vibe available \'React, auth\'" to signal you\'re open to chat about topics.',
|
|
355
|
+
'💡 **Tip:** Use "vibe context --file auth.js" to share what you\'re working on.',
|
|
356
|
+
'💡 **Tip:** Say "start presence monitor" for real-time alerts when people come online.'
|
|
357
|
+
];
|
|
358
|
+
const tipIndex = Math.floor(Date.now() / 60000) % tips.length; // Rotate every minute
|
|
359
|
+
display += `\n\n---\n${tips[tipIndex]}`;
|
|
269
360
|
|
|
270
361
|
// Build response with hints for structured dashboard flow
|
|
271
362
|
const response = { display };
|
|
272
363
|
|
|
364
|
+
// === ENRICHED DATA ===
|
|
365
|
+
// Include full online users list so Claude doesn't need to call vibe_who
|
|
366
|
+
response.onlineUsers = others.map(u => ({
|
|
367
|
+
handle: u.handle,
|
|
368
|
+
building: u.one_liner || u.note || null,
|
|
369
|
+
status: u.status || null,
|
|
370
|
+
lastActive: u.lastSeen ? new Date(u.lastSeen).toISOString() : null
|
|
371
|
+
}));
|
|
372
|
+
|
|
373
|
+
// Include unread thread summaries so Claude doesn't need to call vibe_inbox
|
|
374
|
+
const unreadSenders = inboxThreads.filter(t => t.unread > 0);
|
|
375
|
+
response.unreadThreads = unreadSenders.map(t => ({
|
|
376
|
+
handle: t.handle,
|
|
377
|
+
unread: t.unread,
|
|
378
|
+
preview: t.lastMessage ? t.lastMessage.slice(0, 80) : null,
|
|
379
|
+
isAgent: t.isAgent || false
|
|
380
|
+
}));
|
|
381
|
+
|
|
382
|
+
// Include connection suggestions for smart discovery
|
|
383
|
+
if (suggestions.length > 0) {
|
|
384
|
+
response.suggestions = suggestions.map(s => ({
|
|
385
|
+
handle: s.handle,
|
|
386
|
+
reason: s.reason,
|
|
387
|
+
githubName: s.github_name || null,
|
|
388
|
+
lastActive: s.last_active || null,
|
|
389
|
+
matchScore: s.score || null
|
|
390
|
+
}));
|
|
391
|
+
}
|
|
392
|
+
|
|
273
393
|
// Determine session state and suggest appropriate flow
|
|
274
394
|
let suggestion = null;
|
|
275
395
|
|
|
@@ -304,18 +424,33 @@ async function handler(args) {
|
|
|
304
424
|
|
|
305
425
|
if (others.length === 0 && unreadCount === 0) {
|
|
306
426
|
// Empty room
|
|
307
|
-
actionList = actions.emptyRoom();
|
|
427
|
+
actionList = actions.emptyRoom({ workContext });
|
|
308
428
|
} else {
|
|
309
429
|
// Normal dashboard
|
|
310
430
|
actionList = actions.dashboard({
|
|
311
431
|
unreadCount,
|
|
312
432
|
onlineUsers: onlineHandles,
|
|
313
|
-
suggestion
|
|
433
|
+
suggestion,
|
|
434
|
+
workContext
|
|
314
435
|
});
|
|
315
436
|
}
|
|
316
437
|
|
|
317
438
|
response.actions = formatActions(actionList);
|
|
318
439
|
|
|
440
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
441
|
+
// WORK CONTEXT: Include in response for Claude to use
|
|
442
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
443
|
+
if (workContext?.suggestions?.brief) {
|
|
444
|
+
response.workContext = {
|
|
445
|
+
summary: workContext.suggestions.brief,
|
|
446
|
+
detailed: workContext.suggestions.detailed,
|
|
447
|
+
project: workContext.project?.name,
|
|
448
|
+
branch: workContext.git?.branch,
|
|
449
|
+
recentCommit: workContext.git?.recentCommits?.[0]?.message || null,
|
|
450
|
+
hasUncommitted: workContext.git?.hasUncommitted || false
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
319
454
|
return response;
|
|
320
455
|
}
|
|
321
456
|
|
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 });
|