slashvibe-mcp 0.2.9 → 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 +1 -0
- 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 +154 -5
- package/crypto.js +3 -8
- 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 +105 -216
- package/intelligence/index.js +45 -0
- package/intelligence/infer.js +316 -0
- package/intelligence/interests.js +369 -0
- package/intelligence/patterns.js +651 -0
- package/intelligence/proactive.js +358 -0
- package/intelligence/serendipity.js +306 -0
- package/memory.js +166 -0
- package/notification-emitter.js +77 -0
- package/notify.js +102 -1
- package/package.json +21 -7
- package/prompts.js +1 -1
- package/protocol/index.js +161 -1
- package/setup.js +402 -0
- package/store/api.js +528 -82
- package/store/profiles.js +160 -12
- package/tools/_actions.js +463 -16
- package/tools/_deprecated/auto-suggest-connections.js +304 -0
- package/tools/_deprecated/bootstrap-skills.js +231 -0
- package/tools/_deprecated/bridge-dashboard.js +342 -0
- package/tools/_deprecated/bridge-health.js +400 -0
- package/tools/_deprecated/bridge-live.js +384 -0
- package/tools/_deprecated/bridges.js +383 -0
- package/tools/_deprecated/colorguess.js +281 -0
- package/tools/_deprecated/discover-insights.js +379 -0
- package/tools/_deprecated/discover-momentum.js +256 -0
- package/tools/_deprecated/discovery-analytics.js +345 -0
- package/tools/_deprecated/discovery-auto-suggest.js +275 -0
- package/tools/_deprecated/discovery-bootstrap.js +267 -0
- package/tools/_deprecated/discovery-daily.js +375 -0
- package/tools/_deprecated/discovery-dashboard.js +385 -0
- package/tools/_deprecated/discovery-digest.js +314 -0
- package/tools/_deprecated/discovery-hub.js +357 -0
- package/tools/_deprecated/discovery-insights.js +384 -0
- package/tools/_deprecated/discovery-momentum.js +281 -0
- package/tools/_deprecated/discovery-monitor.js +319 -0
- package/tools/_deprecated/discovery-proactive.js +300 -0
- package/tools/_deprecated/draw.js +317 -0
- package/tools/_deprecated/farcaster.js +307 -0
- package/tools/_deprecated/forget.js +119 -0
- package/tools/_deprecated/games-catalog.js +376 -0
- package/tools/_deprecated/games.js +313 -0
- package/tools/_deprecated/guessnumber.js +194 -0
- package/tools/_deprecated/hangman.js +129 -0
- package/tools/_deprecated/multiplayer-tictactoe.js +303 -0
- package/tools/_deprecated/recall.js +147 -0
- package/tools/_deprecated/remember.js +86 -0
- package/tools/_deprecated/riddle.js +240 -0
- package/tools/_deprecated/run-bootstrap.js +69 -0
- package/tools/_deprecated/skills-analytics.js +349 -0
- package/tools/_deprecated/skills-bootstrap.js +301 -0
- package/tools/_deprecated/skills-dashboard.js +268 -0
- package/tools/_deprecated/skills.js +380 -0
- package/tools/_deprecated/smart-intro.js +353 -0
- package/tools/_deprecated/storybuilder.js +331 -0
- package/tools/_deprecated/telegram-bot.js +183 -0
- package/tools/_deprecated/telegram-setup.js +214 -0
- package/tools/_deprecated/twentyquestions.js +143 -0
- package/tools/_discovery-enhanced.js +290 -0
- package/tools/_discovery.js +439 -0
- package/tools/_proactive-discovery.js +301 -0
- 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/admin-inbox.js +218 -0
- package/tools/agent-treasury.js +288 -0
- package/tools/analytics.js +191 -0
- package/tools/approve.js +197 -0
- package/tools/arcade.js +173 -0
- package/tools/artifact-create.js +17 -11
- package/tools/artifact-view.js +31 -1
- package/tools/artifacts-price.js +47 -43
- package/tools/ask-expert.js +160 -0
- package/tools/available.js +120 -0
- package/tools/become-expert.js +150 -0
- package/tools/broadcast.js +286 -0
- package/tools/chat.js +202 -0
- package/tools/collaborative-drawing.js +286 -0
- package/tools/connection-status.js +178 -0
- package/tools/crossword.js +17 -1
- package/tools/discover.js +350 -34
- package/tools/dm.js +115 -73
- package/tools/drawing.js +1 -1
- package/tools/earnings.js +126 -0
- package/tools/echo.js +16 -0
- package/tools/feed.js +51 -7
- package/tools/follow.js +224 -0
- package/tools/friends.js +207 -0
- package/tools/game.js +2 -2
- package/tools/genesis.js +233 -0
- package/tools/gig-browse.js +206 -0
- package/tools/gig-complete.js +144 -0
- package/tools/handoff.js +7 -1
- package/tools/help.js +4 -4
- package/tools/idea.js +9 -2
- package/tools/inbox.js +334 -28
- package/tools/init.js +727 -36
- package/tools/invite.js +15 -4
- package/tools/l2-bridge.js +272 -0
- package/tools/l2-status.js +217 -0
- package/tools/l2.js +206 -0
- package/tools/migrate.js +156 -0
- package/tools/mint.js +377 -0
- package/tools/multiplayer-game.js +1 -1
- package/tools/notifications.js +415 -0
- package/tools/observe.js +200 -0
- package/tools/onboarding.js +147 -0
- package/tools/open.js +143 -12
- package/tools/party-game.js +2 -2
- package/tools/plan.js +225 -0
- package/tools/presence-agent.js +7 -0
- package/tools/proof-of-work.js +100 -104
- package/tools/pulse.js +218 -0
- package/tools/reply.js +166 -0
- package/tools/report.js +2 -2
- package/tools/reputation.js +175 -0
- 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 +126 -3
- package/tools/ship.js +31 -8
- package/tools/shipback.js +326 -0
- package/tools/smart-check.js +201 -0
- package/tools/social-processor.js +445 -0
- package/tools/solo-game.js +1 -1
- package/tools/start.js +335 -93
- 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 +257 -0
- package/tools/tip.js +193 -0
- package/tools/token.js +103 -0
- package/tools/update.js +1 -1
- package/tools/wallet.js +239 -186
- package/tools/watch.js +157 -0
- package/tools/webhook-test.js +388 -0
- package/tools/who.js +54 -3
- package/tools/withdraw.js +145 -0
- package/tools/work-summary.js +96 -0
- package/tools/workshop.js +327 -0
- package/tools/x-mentions.js +1 -1
- package/version.json +14 -3
- package/tools/artifacts-buy.js +0 -111
- package/tools/connect.js +0 -284
- package/tools/gigs-apply.js +0 -99
- package/tools/gigs-browse.js +0 -114
- package/tools/gigs-complete.js +0 -128
- package/tools/gigs-post.js +0 -140
- package/tools/live-off.js +0 -109
- package/tools/live-watch.js +0 -176
- package/tools/live.js +0 -128
- package/tools/sessions-browse.js +0 -105
- package/tools/whats-happening.js +0 -125
- /package/tools/{away.js → _deprecated/away.js} +0 -0
- /package/tools/{back.js → _deprecated/back.js} +0 -0
- /package/tools/{mute.js → _deprecated/mute.js} +0 -0
- /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
- /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
- /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
package/tools/start.js
CHANGED
|
@@ -8,24 +8,164 @@
|
|
|
8
8
|
* 4. Suggest someone to connect with
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { execSync } = require('child_process');
|
|
11
14
|
const config = require('../config');
|
|
12
15
|
const store = require('../store');
|
|
13
16
|
const memory = require('../memory');
|
|
14
17
|
const notify = require('../notify');
|
|
15
18
|
const patterns = require('../intelligence/patterns');
|
|
16
19
|
const { actions, formatActions } = require('./_actions');
|
|
20
|
+
const init = require('./init');
|
|
21
|
+
const { gatherWithTimeout } = require('./_work-context');
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
if (timestamp === undefined || timestamp === null || isNaN(timestamp)) return 'unknown';
|
|
23
|
+
const REPO_DIR = path.join(process.env.HOME, '.vibe', 'vibe-repo');
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Auto-update on session start
|
|
27
|
+
* Checks for updates and applies them automatically via git pull
|
|
28
|
+
* Returns update info if an update was applied, null otherwise
|
|
29
|
+
*/
|
|
30
|
+
async function autoUpdate() {
|
|
31
|
+
try {
|
|
32
|
+
// Check if we're in a git repo
|
|
33
|
+
const gitDir = path.join(REPO_DIR, '.git');
|
|
34
|
+
if (!fs.existsSync(gitDir)) {
|
|
35
|
+
return null; // Not a git install, skip
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check last update time (don't check more than once per hour)
|
|
39
|
+
const lastCheck = config.get('lastAutoUpdateCheck');
|
|
40
|
+
if (lastCheck && Date.now() - lastCheck < 60 * 60 * 1000) {
|
|
41
|
+
return null; // Checked recently
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Fetch latest from remote (without merging)
|
|
45
|
+
execSync('git fetch origin main', { cwd: REPO_DIR, stdio: 'ignore', timeout: 10000 });
|
|
46
|
+
|
|
47
|
+
// Check if we're behind
|
|
48
|
+
const localHead = execSync('git rev-parse HEAD', { cwd: REPO_DIR, encoding: 'utf8' }).trim();
|
|
49
|
+
const remoteHead = execSync('git rev-parse origin/main', { cwd: REPO_DIR, encoding: 'utf8' }).trim();
|
|
50
|
+
|
|
51
|
+
if (localHead === remoteHead) {
|
|
52
|
+
config.set('lastAutoUpdateCheck', Date.now());
|
|
53
|
+
return null; // Already up to date
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Get current version before update
|
|
57
|
+
const versionPath = path.join(REPO_DIR, 'mcp-server', 'version.json');
|
|
58
|
+
let oldVersion = 'unknown';
|
|
59
|
+
try {
|
|
60
|
+
oldVersion = JSON.parse(fs.readFileSync(versionPath, 'utf8')).version;
|
|
61
|
+
} catch (e) {}
|
|
62
|
+
|
|
63
|
+
// Pull the update
|
|
64
|
+
execSync('git pull origin main', { cwd: REPO_DIR, stdio: 'ignore', timeout: 30000 });
|
|
65
|
+
|
|
66
|
+
// Get new version
|
|
67
|
+
let newVersion = 'unknown';
|
|
68
|
+
try {
|
|
69
|
+
newVersion = JSON.parse(fs.readFileSync(versionPath, 'utf8')).version;
|
|
70
|
+
} catch (e) {}
|
|
71
|
+
|
|
72
|
+
config.set('lastAutoUpdateCheck', Date.now());
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
updated: true,
|
|
76
|
+
from: oldVersion,
|
|
77
|
+
to: newVersion
|
|
78
|
+
};
|
|
79
|
+
} catch (e) {
|
|
80
|
+
// Silent fail - don't block startup
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check for version updates (cached for the session)
|
|
87
|
+
* Non-blocking - returns null on any error
|
|
88
|
+
*/
|
|
89
|
+
async function getVersionInfo() {
|
|
90
|
+
// Check cache first (avoid repeated API calls)
|
|
91
|
+
const cached = config.get('versionInfo');
|
|
92
|
+
if (cached && cached.checkedAt > Date.now() - 5 * 60 * 1000) {
|
|
93
|
+
return cached;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// Read local version
|
|
98
|
+
const versionPath = path.join(__dirname, '..', 'version.json');
|
|
99
|
+
const localVersion = JSON.parse(fs.readFileSync(versionPath, 'utf8'));
|
|
100
|
+
const current = localVersion.version;
|
|
101
|
+
|
|
102
|
+
// Fetch remote version (with timeout)
|
|
103
|
+
const controller = new AbortController();
|
|
104
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
105
|
+
|
|
106
|
+
const response = await fetch(`${config.getApiUrl()}/api/version`, {
|
|
107
|
+
headers: { 'User-Agent': 'vibe-mcp-client' },
|
|
108
|
+
signal: controller.signal
|
|
109
|
+
});
|
|
110
|
+
clearTimeout(timeout);
|
|
111
|
+
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
// Cache failure to avoid retrying
|
|
114
|
+
const result = { current, latest: null, hasUpdate: false, checkedAt: Date.now() };
|
|
115
|
+
config.set('versionInfo', result);
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const remoteVersion = await response.json();
|
|
120
|
+
const latest = remoteVersion.version;
|
|
121
|
+
const hasUpdate = compareVersions(latest, current) > 0;
|
|
122
|
+
|
|
123
|
+
const result = { current, latest, hasUpdate, checkedAt: Date.now() };
|
|
124
|
+
config.set('versionInfo', result);
|
|
125
|
+
return result;
|
|
126
|
+
} catch (e) {
|
|
127
|
+
// Silent fail - return local version only
|
|
128
|
+
try {
|
|
129
|
+
const versionPath = path.join(__dirname, '..', 'version.json');
|
|
130
|
+
const localVersion = JSON.parse(fs.readFileSync(versionPath, 'utf8'));
|
|
131
|
+
return { current: localVersion.version, latest: null, hasUpdate: false, checkedAt: Date.now() };
|
|
132
|
+
} catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
23
137
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Compare semver versions: returns 1 if v1 > v2, -1 if v1 < v2, 0 if equal
|
|
140
|
+
*/
|
|
141
|
+
function compareVersions(v1, v2) {
|
|
142
|
+
const parts1 = v1.split('.').map(Number);
|
|
143
|
+
const parts2 = v2.split('.').map(Number);
|
|
144
|
+
for (let i = 0; i < 3; i++) {
|
|
145
|
+
if (parts1[i] > parts2[i]) return 1;
|
|
146
|
+
if (parts1[i] < parts2[i]) return -1;
|
|
147
|
+
}
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Generate ASCII welcome card - matches init.js format
|
|
153
|
+
* Format: logo | handle + unread | tagline + online
|
|
154
|
+
*/
|
|
155
|
+
function generateWelcomeCard({ handle, onlineCount, unreadCount, versionInfo }) {
|
|
156
|
+
// Match init.js generateAuthBanner format for consistency
|
|
157
|
+
const handleCol = `@${handle}`.padEnd(16);
|
|
158
|
+
const unreadCol = unreadCount > 0 ? `📬 ${unreadCount} unread`.padEnd(14) : `📬 0 messages`.padEnd(14);
|
|
159
|
+
|
|
160
|
+
// Add version badge if available
|
|
161
|
+
let versionSuffix = '';
|
|
162
|
+
if (versionInfo?.hasUpdate) {
|
|
163
|
+
versionSuffix = ' ⬆️';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return ` █░█ █ █▄▄ █▀▀ ${handleCol} 🚀 ship together
|
|
167
|
+
▀▄▀ █ █▄█ ██▄ ${unreadCol} 🟢 ${onlineCount} online${versionSuffix}
|
|
168
|
+
──────────────────────────────────────────────────`;
|
|
29
169
|
}
|
|
30
170
|
|
|
31
171
|
const definition = {
|
|
@@ -47,90 +187,78 @@ const definition = {
|
|
|
47
187
|
};
|
|
48
188
|
|
|
49
189
|
async function handler(args) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// Step 1:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- **Building**: What are you working on?
|
|
62
|
-
|
|
63
|
-
Example: "I'm @davemorin, working on social apps"`
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Initialize with provided handle
|
|
68
|
-
const h = args.handle.toLowerCase().replace('@', '').replace(/[^a-z0-9_-]/g, '');
|
|
69
|
-
const oneLiner = args.building || 'building something';
|
|
70
|
-
|
|
71
|
-
config.setSessionIdentity(h, oneLiner);
|
|
72
|
-
const cfg = config.load();
|
|
73
|
-
cfg.handle = h;
|
|
74
|
-
cfg.one_liner = oneLiner;
|
|
75
|
-
cfg.visible = true;
|
|
76
|
-
config.save(cfg);
|
|
190
|
+
// Step 0: Auto-update check (runs git pull if behind)
|
|
191
|
+
const updateResult = await autoUpdate();
|
|
192
|
+
|
|
193
|
+
// Step 1: Check if properly authenticated with OAuth
|
|
194
|
+
// If not, redirect to init for GitHub auth flow (shows pre-auth banner + OAuth)
|
|
195
|
+
if (!config.hasOAuth()) {
|
|
196
|
+
return init.handler({
|
|
197
|
+
handle: args.handle,
|
|
198
|
+
one_liner: args.building
|
|
199
|
+
});
|
|
200
|
+
}
|
|
77
201
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
202
|
+
// Step 2: User is authenticated - show dashboard
|
|
203
|
+
const myHandle = config.getHandle();
|
|
204
|
+
let threads = [];
|
|
205
|
+
let updateNotice = '';
|
|
81
206
|
|
|
82
|
-
|
|
83
|
-
|
|
207
|
+
// If we just updated, show a notice
|
|
208
|
+
if (updateResult?.updated) {
|
|
209
|
+
updateNotice = `\n\n⬆️ **Updated v${updateResult.from} → v${updateResult.to}** — restart Claude Code to apply`;
|
|
210
|
+
}
|
|
84
211
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
} else {
|
|
88
|
-
// ═══ RETURNING USER — Session Rehydration ═══
|
|
89
|
-
const oneLiner = config.getOneLiner() || 'building something';
|
|
90
|
-
display += `## Welcome back, @${myHandle}\n\n`;
|
|
91
|
-
display += `_${oneLiner}_\n\n`;
|
|
212
|
+
// Fetch version info early (non-blocking, cached)
|
|
213
|
+
const versionInfo = await getVersionInfo().catch(() => null);
|
|
92
214
|
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
95
220
|
|
|
96
|
-
|
|
221
|
+
if (autoContextEnabled) {
|
|
97
222
|
try {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
103
234
|
}
|
|
104
|
-
} catch (e) {
|
|
235
|
+
} catch (e) {
|
|
236
|
+
// Silent fail - context is nice-to-have, not required
|
|
237
|
+
workContext = null;
|
|
238
|
+
}
|
|
105
239
|
}
|
|
106
240
|
|
|
241
|
+
// Log session start for patterns
|
|
242
|
+
patterns.logSessionStart(myHandle);
|
|
243
|
+
|
|
244
|
+
// Get threads for memory context
|
|
245
|
+
try {
|
|
246
|
+
threads = memory.listThreads();
|
|
247
|
+
} catch (e) {}
|
|
248
|
+
|
|
107
249
|
// Step 2: Get who's around
|
|
108
250
|
const users = await store.getActiveUsers();
|
|
109
251
|
const others = users.filter(u => u.handle !== myHandle);
|
|
110
252
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
display += `_No one else here yet._ Invite someone: slashvibe.dev\n\n`;
|
|
115
|
-
} else {
|
|
116
|
-
others.slice(0, 5).forEach(u => {
|
|
117
|
-
const mood = u.mood ? ` ${u.mood}` : '';
|
|
118
|
-
const xLink = `[x.com/${u.handle}](https://x.com/${u.handle})`;
|
|
119
|
-
const context = u.note || u.one_liner || 'building';
|
|
120
|
-
display += `● **@${u.handle}**${mood} — ${context}\n ${xLink} — _${formatTimeAgo(u.lastSeen)}_\n\n`;
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
if (others.length > 5) {
|
|
124
|
-
display += `_...and ${others.length - 5} more_\n\n`;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Step 3: Check inbox + trigger notifications for old unread
|
|
253
|
+
// Step 3: Check inbox + trigger notifications
|
|
254
|
+
let unreadCount = 0;
|
|
255
|
+
let inboxThreads = [];
|
|
129
256
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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);
|
|
133
260
|
|
|
261
|
+
if (unreadCount > 0) {
|
|
134
262
|
// Check for messages needing desktop notification escalation
|
|
135
263
|
const rawInbox = await store.getRawInbox(myHandle).catch(() => []);
|
|
136
264
|
if (rawInbox.length > 0) {
|
|
@@ -139,31 +267,130 @@ Example: "I'm @davemorin, working on social apps"`
|
|
|
139
267
|
}
|
|
140
268
|
} catch (e) {}
|
|
141
269
|
|
|
142
|
-
// Step 4:
|
|
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
|
+
|
|
288
|
+
// Generate the ASCII welcome card (matches init.js format)
|
|
289
|
+
const welcomeCard = generateWelcomeCard({
|
|
290
|
+
handle: myHandle,
|
|
291
|
+
onlineCount: others.length,
|
|
292
|
+
unreadCount,
|
|
293
|
+
versionInfo
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// Build display with card + any additional info
|
|
297
|
+
let display = welcomeCard;
|
|
298
|
+
|
|
299
|
+
// Add who's online section (top 5 with what they're building)
|
|
143
300
|
if (others.length > 0) {
|
|
144
|
-
const
|
|
145
|
-
display +=
|
|
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
|
+
}
|
|
146
312
|
}
|
|
147
313
|
|
|
148
|
-
//
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
display += `\n\n
|
|
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
|
+
});
|
|
152
337
|
}
|
|
153
338
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
339
|
+
// Add memory context for returning users
|
|
340
|
+
if (threads.length > 0) {
|
|
341
|
+
const recentThreads = threads.slice(0, 3);
|
|
342
|
+
const names = recentThreads.map(t => `@${t.handle}`).join(', ');
|
|
343
|
+
display += `\n\n💭 **${threads.length}** people in memory · ${names}`;
|
|
344
|
+
}
|
|
157
345
|
|
|
158
|
-
|
|
159
|
-
|
|
346
|
+
// Add update notice if we just auto-updated
|
|
347
|
+
if (updateNotice) {
|
|
348
|
+
display += updateNotice;
|
|
160
349
|
}
|
|
161
350
|
|
|
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]}`;
|
|
360
|
+
|
|
162
361
|
// Build response with hints for structured dashboard flow
|
|
163
362
|
const response = { display };
|
|
164
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
|
+
|
|
165
393
|
// Determine session state and suggest appropriate flow
|
|
166
|
-
const unreadCount = await store.getUnreadCount(myHandle).catch(() => 0);
|
|
167
394
|
let suggestion = null;
|
|
168
395
|
|
|
169
396
|
if (unreadCount >= 5) {
|
|
@@ -197,18 +424,33 @@ Example: "I'm @davemorin, working on social apps"`
|
|
|
197
424
|
|
|
198
425
|
if (others.length === 0 && unreadCount === 0) {
|
|
199
426
|
// Empty room
|
|
200
|
-
actionList = actions.emptyRoom();
|
|
427
|
+
actionList = actions.emptyRoom({ workContext });
|
|
201
428
|
} else {
|
|
202
429
|
// Normal dashboard
|
|
203
430
|
actionList = actions.dashboard({
|
|
204
431
|
unreadCount,
|
|
205
432
|
onlineUsers: onlineHandles,
|
|
206
|
-
suggestion
|
|
433
|
+
suggestion,
|
|
434
|
+
workContext
|
|
207
435
|
});
|
|
208
436
|
}
|
|
209
437
|
|
|
210
438
|
response.actions = formatActions(actionList);
|
|
211
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
|
+
|
|
212
454
|
return response;
|
|
213
455
|
}
|
|
214
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 });
|