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/proof-of-work.js
CHANGED
|
@@ -1,148 +1,144 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* vibe_proof_of_work - Get unified proof-of-work summary for a user
|
|
3
3
|
*
|
|
4
|
-
* Shows
|
|
5
|
-
* - PAST: Artifacts created/sold, sessions shared
|
|
6
|
-
* - PRESENT:
|
|
7
|
-
* - FUTURE: Gigs completed, hire availability
|
|
8
|
-
*
|
|
9
|
-
* Part of /vibe's native monetization layer.
|
|
4
|
+
* Shows the complete "proof of work" picture:
|
|
5
|
+
* - PAST: Artifacts created/sold, sessions shared
|
|
6
|
+
* - PRESENT: Live presence, current work
|
|
7
|
+
* - FUTURE: Gigs completed/posted, hire availability
|
|
8
|
+
* - UNIFIED: Vibe score, tier, proven skills
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
11
|
const config = require('../config');
|
|
13
|
-
const { requireInit } = require('./_shared');
|
|
12
|
+
const { requireInit, normalizeHandle, displayHandle, header, divider, emptyState, formatTimeAgo } = require('./_shared');
|
|
13
|
+
const { actions, formatActions } = require('./_actions');
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const BASE_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
|
|
16
16
|
|
|
17
17
|
const definition = {
|
|
18
18
|
name: 'vibe_proof_of_work',
|
|
19
|
-
description: '
|
|
19
|
+
description: 'Get proof-of-work summary for a user. Shows artifacts, sessions, gigs, vibe score, and proven skills.',
|
|
20
20
|
inputSchema: {
|
|
21
21
|
type: 'object',
|
|
22
22
|
properties: {
|
|
23
23
|
handle: {
|
|
24
24
|
type: 'string',
|
|
25
|
-
description: '
|
|
25
|
+
description: 'User handle to look up (default: yourself)'
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
// Tier emoji mapping
|
|
32
|
-
const TIER_EMOJI = {
|
|
33
|
-
newcomer: 'š±',
|
|
34
|
-
rising: 'š',
|
|
35
|
-
proven: 'ā
',
|
|
36
|
-
established: 'ā',
|
|
37
|
-
expert: 'š'
|
|
38
|
-
};
|
|
39
|
-
|
|
40
31
|
async function handler(args) {
|
|
41
32
|
const initCheck = requireInit();
|
|
42
33
|
if (initCheck) return initCheck;
|
|
43
34
|
|
|
44
|
-
const
|
|
45
|
-
const normalizedHandle = handle.replace('@', '').toLowerCase();
|
|
46
|
-
const isOwnProfile = normalizedHandle === config.getHandle();
|
|
35
|
+
const targetHandle = normalizeHandle(args.handle) || config.getHandle();
|
|
47
36
|
|
|
48
37
|
try {
|
|
49
|
-
const response = await fetch(`${
|
|
38
|
+
const response = await fetch(`${BASE_URL}/api/proof-of-work?handle=${encodeURIComponent(targetHandle)}`);
|
|
50
39
|
const data = await response.json();
|
|
51
40
|
|
|
52
41
|
if (!data.success) {
|
|
53
|
-
return {
|
|
54
|
-
display: `ā Could not load proof-of-work: ${data.error || 'User not found'}`
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const tierEmoji = TIER_EMOJI[data.tier] || 'š±';
|
|
59
|
-
|
|
60
|
-
let display = `## ${tierEmoji} Proof of Work: @${data.handle}\n\n`;
|
|
61
|
-
|
|
62
|
-
// Overall score and tier
|
|
63
|
-
display += `**Score:** ${data.proof_of_work_score} points\n`;
|
|
64
|
-
display += `**Tier:** ${data.tier.charAt(0).toUpperCase() + data.tier.slice(1)}\n\n`;
|
|
65
|
-
|
|
66
|
-
// PAST
|
|
67
|
-
display += `### š PAST (Artifacts & Sessions)\n`;
|
|
68
|
-
display += `- Artifacts created: ${data.artifacts.created}\n`;
|
|
69
|
-
display += `- For sale: ${data.artifacts.for_sale}\n`;
|
|
70
|
-
display += `- Sold: ${data.artifacts.sold}\n`;
|
|
71
|
-
if (data.artifacts.revenue_cents > 0) {
|
|
72
|
-
display += `- Revenue: $${(data.artifacts.revenue_cents / 100).toFixed(2)}\n`;
|
|
42
|
+
return { display: `ā ${data.error || 'Failed to fetch proof-of-work'}` };
|
|
73
43
|
}
|
|
74
|
-
display += `- Sessions shared: ${data.sessions.shared}\n\n`;
|
|
75
44
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
45
|
+
const pow = data.proof_of_work;
|
|
46
|
+
const isSelf = targetHandle === config.getHandle();
|
|
47
|
+
|
|
48
|
+
// Build display
|
|
49
|
+
let display = header(`Proof of Work: ${displayHandle(targetHandle)}`, 2) + '\n\n';
|
|
50
|
+
|
|
51
|
+
// Vibe Score & Tier
|
|
52
|
+
display += `**Vibe Score:** ${pow.unified?.vibe_score || 0}\n`;
|
|
53
|
+
display += `**Tier:** ${formatTier(pow.unified?.tier)}\n\n`;
|
|
54
|
+
|
|
55
|
+
// āāā PAST āāā
|
|
56
|
+
display += header('š PAST (Artifacts & Sessions)', 3) + '\n';
|
|
57
|
+
const past = pow.past || {};
|
|
58
|
+
display += `⢠**Artifacts Created:** ${past.artifacts_created || 0}\n`;
|
|
59
|
+
display += `⢠**Artifacts Sold:** ${past.artifacts_sold || 0}\n`;
|
|
60
|
+
display += `⢠**Revenue Earned:** $${((past.revenue_cents || 0) / 100).toFixed(2)}\n`;
|
|
61
|
+
display += `⢠**Sessions Shared:** ${past.sessions_shared || 0}\n\n`;
|
|
62
|
+
|
|
63
|
+
// āāā PRESENT āāā
|
|
64
|
+
display += header('š¢ PRESENT (Live)', 3) + '\n';
|
|
65
|
+
const present = pow.present || {};
|
|
66
|
+
if (present.is_online) {
|
|
67
|
+
display += `⢠**Status:** Online\n`;
|
|
68
|
+
if (present.working_on) {
|
|
69
|
+
display += `⢠**Working on:** ${present.working_on}\n`;
|
|
70
|
+
}
|
|
71
|
+
if (present.project) {
|
|
72
|
+
display += `⢠**Project:** ${present.project}\n`;
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
display += `⢠**Status:** Offline\n`;
|
|
76
|
+
if (present.last_seen) {
|
|
77
|
+
display += `⢠**Last seen:** ${formatTimeAgo(present.last_seen)}\n`;
|
|
78
|
+
}
|
|
81
79
|
}
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
display += '\n';
|
|
81
|
+
|
|
82
|
+
// āāā FUTURE āāā
|
|
83
|
+
display += header('š FUTURE (Gigs & Availability)', 3) + '\n';
|
|
84
|
+
const future = pow.future || {};
|
|
85
|
+
display += `⢠**Gigs Completed:** ${future.gigs_completed || 0}\n`;
|
|
86
|
+
display += `⢠**Gigs Posted:** ${future.gigs_posted || 0}\n`;
|
|
87
|
+
display += `⢠**Avg Rating:** ${future.avg_rating ? `${future.avg_rating.toFixed(1)}ā` : 'No ratings yet'}\n`;
|
|
88
|
+
display += `⢠**Available for Hire:** ${future.available_for_hire ? 'Yes' : 'No'}\n\n`;
|
|
89
|
+
|
|
90
|
+
// āāā PROVEN SKILLS āāā
|
|
91
|
+
const skills = pow.unified?.proven_skills || [];
|
|
92
|
+
if (skills.length > 0) {
|
|
93
|
+
display += header('š ļø Proven Skills', 3) + '\n';
|
|
94
|
+
skills.slice(0, 10).forEach(skill => {
|
|
95
|
+
display += `⢠${skill.name} (${skill.count} gig${skill.count !== 1 ? 's' : ''})\n`;
|
|
96
|
+
});
|
|
97
|
+
display += '\n';
|
|
84
98
|
}
|
|
85
|
-
display += `\n`;
|
|
86
99
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
display +=
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
display +=
|
|
100
|
+
// āāā SCORE BREAKDOWN āāā
|
|
101
|
+
const breakdown = pow.unified?.score_breakdown;
|
|
102
|
+
if (breakdown) {
|
|
103
|
+
display += header('š Score Breakdown', 3) + '\n';
|
|
104
|
+
display += `⢠Gigs: ${breakdown.gigs || 0}\n`;
|
|
105
|
+
display += `⢠Artifacts: ${breakdown.artifacts || 0}\n`;
|
|
106
|
+
display += `⢠Sessions: ${breakdown.sessions || 0}\n`;
|
|
107
|
+
display += `⢠Invites: ${breakdown.invites || 0}\n`;
|
|
108
|
+
display += `⢠Ships: ${breakdown.ships || 0}\n`;
|
|
95
109
|
}
|
|
96
|
-
display += `- Available for hire: ${data.hire.available ? 'Yes' : 'No'}\n`;
|
|
97
|
-
if (data.hire.rate) {
|
|
98
|
-
display += `- Hourly rate: $${data.hire.rate}\n`;
|
|
99
|
-
}
|
|
100
|
-
if (data.hire.skills && data.hire.skills.length > 0) {
|
|
101
|
-
display += `- Skills: ${data.hire.skills.join(', ')}\n`;
|
|
102
|
-
}
|
|
103
|
-
display += `\n`;
|
|
104
110
|
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
display +=
|
|
109
|
-
skills.sort((a, b) => b[1] - a[1]);
|
|
110
|
-
skills.slice(0, 5).forEach(([skill, level]) => {
|
|
111
|
-
const bars = 'ā'.repeat(Math.min(level, 5)) + 'ā'.repeat(5 - Math.min(level, 5));
|
|
112
|
-
display += `- ${skill}: ${bars} (${level})\n`;
|
|
113
|
-
});
|
|
114
|
-
display += `\n`;
|
|
111
|
+
// CTA for self
|
|
112
|
+
if (isSelf) {
|
|
113
|
+
display += divider();
|
|
114
|
+
display += 'š” **Boost your score:** Complete gigs (+50), sell artifacts (+30), share sessions (+10)\n';
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
//
|
|
118
|
-
display
|
|
119
|
-
|
|
120
|
-
if (data.artifacts.for_sale === 0) {
|
|
121
|
-
display += `š” **Tip:** Price your artifacts to earn from your work!\n`;
|
|
122
|
-
}
|
|
123
|
-
if (data.gigs.completed === 0) {
|
|
124
|
-
display += `š” **Tip:** Complete gigs to boost your reputation and tier.\n`;
|
|
125
|
-
}
|
|
126
|
-
} else {
|
|
127
|
-
if (data.hire.available) {
|
|
128
|
-
display += `š© DM @${data.handle} to discuss a project\n`;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
117
|
+
// Build result with tip actions
|
|
118
|
+
const result = { display };
|
|
119
|
+
result.actions = formatActions(actions.afterProofOfWork(targetHandle, isSelf));
|
|
131
120
|
|
|
132
|
-
return
|
|
133
|
-
display,
|
|
134
|
-
data: {
|
|
135
|
-
handle: data.handle,
|
|
136
|
-
score: data.proof_of_work_score,
|
|
137
|
-
tier: data.tier
|
|
138
|
-
}
|
|
139
|
-
};
|
|
121
|
+
return result;
|
|
140
122
|
|
|
141
123
|
} catch (e) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
};
|
|
124
|
+
console.error('[proof-of-work] Error:', e);
|
|
125
|
+
return { display: `ā Failed to fetch proof-of-work: ${e.message}` };
|
|
145
126
|
}
|
|
146
127
|
}
|
|
147
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Format tier with emoji
|
|
131
|
+
*/
|
|
132
|
+
function formatTier(tier) {
|
|
133
|
+
const tiers = {
|
|
134
|
+
newcomer: 'š± Newcomer',
|
|
135
|
+
rising: 'š Rising',
|
|
136
|
+
proven: 'ā
Proven',
|
|
137
|
+
established: 'š Established',
|
|
138
|
+
expert: 'ā Expert',
|
|
139
|
+
legendary: 'š Legendary'
|
|
140
|
+
};
|
|
141
|
+
return tiers[tier] || tier || 'Unknown';
|
|
142
|
+
}
|
|
143
|
+
|
|
148
144
|
module.exports = { definition, handler };
|
package/tools/pulse.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe pulse ā Lightweight presence check for inline display
|
|
3
|
+
*
|
|
4
|
+
* Returns a compact "mini card" showing who's online.
|
|
5
|
+
* Designed to be called periodically by Claude based on CLAUDE.md instructions.
|
|
6
|
+
*
|
|
7
|
+
* Unlike vibe_who (full presence), this is:
|
|
8
|
+
* - Faster (minimal data)
|
|
9
|
+
* - Compact (fits in a response footer)
|
|
10
|
+
* - Suggestive (gentle nudges to connect)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const config = require('../config');
|
|
14
|
+
const store = require('../store');
|
|
15
|
+
|
|
16
|
+
const definition = {
|
|
17
|
+
name: 'vibe_pulse',
|
|
18
|
+
description: 'Quick presence check for inline display. Returns compact mini card showing who\'s online. Call periodically (~every 5 messages) to maintain ambient awareness.',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Gentle nudge templates
|
|
26
|
+
const NUDGES = {
|
|
27
|
+
just_joined: [
|
|
28
|
+
"{handle} just joined ā say hi?",
|
|
29
|
+
"{handle} is here! Quick wave?",
|
|
30
|
+
"New face: {handle} just arrived"
|
|
31
|
+
],
|
|
32
|
+
shipping: [
|
|
33
|
+
"{handle} is shipping ā cheer them on?",
|
|
34
|
+
"{handle} in flow mode š„"
|
|
35
|
+
],
|
|
36
|
+
debugging: [
|
|
37
|
+
"{handle} debugging ā maybe you can help?",
|
|
38
|
+
"{handle} stuck on something"
|
|
39
|
+
],
|
|
40
|
+
active: [
|
|
41
|
+
"{handle} is around",
|
|
42
|
+
"{handle} building right now"
|
|
43
|
+
],
|
|
44
|
+
multiple: [
|
|
45
|
+
"{count} builders online ā you're not alone",
|
|
46
|
+
"{count} people vibing right now",
|
|
47
|
+
"Room's alive: {count} online"
|
|
48
|
+
]
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function pickRandom(arr) {
|
|
52
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function formatNudge(template, vars) {
|
|
56
|
+
let result = template;
|
|
57
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
58
|
+
result = result.replace(`{${key}}`, value);
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getStatusEmoji(user) {
|
|
64
|
+
// Explicit mood takes priority
|
|
65
|
+
if (user.mood === 'š„' || user.mood === 'shipping') return 'š„';
|
|
66
|
+
if (user.mood === 'š' || user.mood === 'debugging') return 'š';
|
|
67
|
+
if (user.mood === 'š§ ' || user.mood === 'thinking') return 'š§ ';
|
|
68
|
+
if (user.mood === 'š' || user.mood === 'celebrating') return 'š';
|
|
69
|
+
if (user.mood === 'š§') return 'š§';
|
|
70
|
+
if (user.mood === 'ā') return 'ā';
|
|
71
|
+
|
|
72
|
+
// Infer from what they're building (one_liner)
|
|
73
|
+
const workingOn = (user.one_liner || '').toLowerCase();
|
|
74
|
+
if (workingOn.includes('ai') || workingOn.includes('agent') || workingOn.includes('llm')) return 'š¤';
|
|
75
|
+
if (workingOn.includes('creative') || workingOn.includes('art') || workingOn.includes('design')) return 'šØ';
|
|
76
|
+
if (workingOn.includes('game') || workingOn.includes('chess') || workingOn.includes('play')) return 'š®';
|
|
77
|
+
if (workingOn.includes('social') || workingOn.includes('chat') || workingOn.includes('message')) return 'š¬';
|
|
78
|
+
if (workingOn.includes('crypto') || workingOn.includes('web3') || workingOn.includes('token')) return 'āļø';
|
|
79
|
+
if (workingOn.includes('tool') || workingOn.includes('platform') || workingOn.includes('infra')) return 'š ļø';
|
|
80
|
+
if (workingOn.includes('mobile') || workingOn.includes('app')) return 'š±';
|
|
81
|
+
if (workingOn.includes('data') || workingOn.includes('analytics')) return 'š';
|
|
82
|
+
|
|
83
|
+
// Infer from builderMode
|
|
84
|
+
if (user.builderMode === 'deep-focus') return 'š§';
|
|
85
|
+
if (user.builderMode === 'shipping') return 'š';
|
|
86
|
+
if (user.builderMode === 'exploring') return 'š';
|
|
87
|
+
if (user.builderMode === 'focused') return 'š»';
|
|
88
|
+
|
|
89
|
+
// Fallback to recency
|
|
90
|
+
const age = Date.now() - user.lastSeen;
|
|
91
|
+
if (age < 2 * 60 * 1000) return 'ā”'; // Active < 2min
|
|
92
|
+
if (age < 5 * 60 * 1000) return 'ā'; // Recent < 5min
|
|
93
|
+
return 'ā'; // Idle
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getUserState(user) {
|
|
97
|
+
const age = Date.now() - user.lastSeen;
|
|
98
|
+
|
|
99
|
+
// Check for just joined (< 2 min session)
|
|
100
|
+
if (user.firstSeen) {
|
|
101
|
+
const sessionDuration = (user.lastSeen - new Date(user.firstSeen).getTime()) / 60000;
|
|
102
|
+
if (sessionDuration < 2 && age < 2 * 60 * 1000) {
|
|
103
|
+
return 'just_joined';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (user.mood === 'š„' || user.mood === 'shipping') return 'shipping';
|
|
108
|
+
if (user.mood === 'š' || user.mood === 'debugging') return 'debugging';
|
|
109
|
+
|
|
110
|
+
return 'active';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function handler(args) {
|
|
114
|
+
// Check if initialized
|
|
115
|
+
if (!config.isInitialized()) {
|
|
116
|
+
return {
|
|
117
|
+
display: null,
|
|
118
|
+
empty: true,
|
|
119
|
+
reason: 'not_initialized'
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const myHandle = config.getHandle();
|
|
124
|
+
|
|
125
|
+
// Fetch presence and unread (wrap in Promise.resolve for local store compat)
|
|
126
|
+
const [users, unreadCount] = await Promise.all([
|
|
127
|
+
Promise.resolve(store.getActiveUsers()).catch(() => []),
|
|
128
|
+
Promise.resolve(store.getUnreadCount(myHandle)).catch(() => 0)
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
const others = users.filter(u => u.handle !== myHandle);
|
|
132
|
+
|
|
133
|
+
// Empty room
|
|
134
|
+
if (others.length === 0 && unreadCount === 0) {
|
|
135
|
+
return {
|
|
136
|
+
display: null,
|
|
137
|
+
empty: true,
|
|
138
|
+
reason: 'no_one_online'
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Smart truncate: break at word boundary, add ellipsis if truncated
|
|
143
|
+
function smartTruncate(text, maxLen) {
|
|
144
|
+
if (!text || text.length <= maxLen) return text || '';
|
|
145
|
+
// Find last space before maxLen
|
|
146
|
+
const truncated = text.slice(0, maxLen);
|
|
147
|
+
const lastSpace = truncated.lastIndexOf(' ');
|
|
148
|
+
if (lastSpace > maxLen * 0.6) {
|
|
149
|
+
// Break at word if we're not losing too much
|
|
150
|
+
return truncated.slice(0, lastSpace) + 'ā¦';
|
|
151
|
+
}
|
|
152
|
+
return truncated.slice(0, maxLen - 1) + 'ā¦';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Get short status label from what they're working on
|
|
156
|
+
function getStatusLabel(user) {
|
|
157
|
+
if (user.mood === 'š„' || user.mood === 'shipping') return 'shipping';
|
|
158
|
+
if (user.mood === 'š' || user.mood === 'debugging') return 'debugging';
|
|
159
|
+
if (user.mood === 'š§ ' || user.mood === 'thinking') return 'thinking';
|
|
160
|
+
if (user.mood === 'š' || user.mood === 'celebrating') return 'shipped!';
|
|
161
|
+
if (user.mood === 'š„' || user.mood === 'pairing') return 'pairing';
|
|
162
|
+
if (user.mood === 'š§') return 'vibing';
|
|
163
|
+
if (user.note) return smartTruncate(user.note, 28);
|
|
164
|
+
if (user.one_liner) return smartTruncate(user.one_liner, 28);
|
|
165
|
+
return '';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Build compact single-line summary optimized for collapsed view
|
|
169
|
+
// This ONE line must contain all key info since details get hidden
|
|
170
|
+
const topUsers = others.slice(0, 4);
|
|
171
|
+
const userChips = topUsers.map(u => `${getStatusEmoji(u)} ${u.handle}`).join(' Ā· ');
|
|
172
|
+
const moreCount = others.length > 4 ? ` +${others.length - 4}` : '';
|
|
173
|
+
const dmsBadge = unreadCount > 0 ? `š¬ ${unreadCount} Ā· ` : '';
|
|
174
|
+
|
|
175
|
+
// Single summary line with all key info
|
|
176
|
+
let display = `${dmsBadge}ā ${others.length} online ā ${userChips}${moreCount}`;
|
|
177
|
+
|
|
178
|
+
// Generate nudge
|
|
179
|
+
let nudge = null;
|
|
180
|
+
if (others.length > 0) {
|
|
181
|
+
// Find most interesting user for nudge
|
|
182
|
+
const states = others.map(u => ({ user: u, state: getUserState(u) }));
|
|
183
|
+
|
|
184
|
+
// Priority: just_joined > shipping > debugging > active
|
|
185
|
+
const priority = ['just_joined', 'shipping', 'debugging', 'active'];
|
|
186
|
+
let picked = null;
|
|
187
|
+
|
|
188
|
+
for (const state of priority) {
|
|
189
|
+
picked = states.find(s => s.state === state);
|
|
190
|
+
if (picked) break;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (picked) {
|
|
194
|
+
const templates = NUDGES[picked.state] || NUDGES.active;
|
|
195
|
+
nudge = formatNudge(pickRandom(templates), {
|
|
196
|
+
handle: `@${picked.user.handle}`,
|
|
197
|
+
count: others.length
|
|
198
|
+
});
|
|
199
|
+
} else if (others.length >= 2) {
|
|
200
|
+
nudge = formatNudge(pickRandom(NUDGES.multiple), { count: others.length });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Add nudge after details
|
|
205
|
+
if (nudge) {
|
|
206
|
+
display += `\n\n_${nudge}_`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
display,
|
|
211
|
+
online_count: others.length,
|
|
212
|
+
unread_count: unreadCount,
|
|
213
|
+
nudge,
|
|
214
|
+
handles: others.slice(0, 5).map(u => u.handle)
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = { definition, handler };
|
package/tools/reply.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe reply ā Quick reply to the most recent unread message
|
|
3
|
+
*
|
|
4
|
+
* Streamlined flow: one command instead of inbox ā open ā dm
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const config = require('../config');
|
|
8
|
+
const store = require('../store');
|
|
9
|
+
const patterns = require('../intelligence/patterns');
|
|
10
|
+
const userProfiles = require('../store/profiles');
|
|
11
|
+
const { trackMessage } = require('./summarize');
|
|
12
|
+
const { requireInit, normalizeHandle, truncate, warning, fetchRelevantUsers } = require('./_shared');
|
|
13
|
+
const { actions, formatActions } = require('./_actions');
|
|
14
|
+
|
|
15
|
+
const definition = {
|
|
16
|
+
name: 'vibe_reply',
|
|
17
|
+
description: 'Quick reply to your most recent unread message, or to a specific person. Streamlined: one command instead of inbox ā open ā dm.',
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
message: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Your reply message'
|
|
24
|
+
},
|
|
25
|
+
to: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Optional: reply to a specific person (e.g., @alex). If not provided, replies to most recent unread.'
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
required: ['message']
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
async function handler(args) {
|
|
35
|
+
const initCheck = requireInit();
|
|
36
|
+
if (initCheck) return initCheck;
|
|
37
|
+
|
|
38
|
+
const { message, to } = args;
|
|
39
|
+
const myHandle = config.getHandle();
|
|
40
|
+
|
|
41
|
+
if (!message || message.trim().length === 0) {
|
|
42
|
+
return { display: 'Need a message to reply with.' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let targetHandle;
|
|
46
|
+
let threadContext = null;
|
|
47
|
+
|
|
48
|
+
// If specific recipient provided, use that
|
|
49
|
+
if (to) {
|
|
50
|
+
targetHandle = normalizeHandle(to);
|
|
51
|
+
} else {
|
|
52
|
+
// Find most recent unread thread
|
|
53
|
+
const threads = await store.getInbox(myHandle);
|
|
54
|
+
|
|
55
|
+
if (!threads || threads.length === 0) {
|
|
56
|
+
return {
|
|
57
|
+
display: 'š No messages to reply to.\n\nUse `vibe dm @someone "message"` to start a conversation.',
|
|
58
|
+
actions: formatActions(actions.recommendedConnections([]))
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Find first thread with unread messages
|
|
63
|
+
const unreadThread = threads.find(t => t.unread > 0);
|
|
64
|
+
|
|
65
|
+
if (!unreadThread) {
|
|
66
|
+
// No unread, show most recent thread
|
|
67
|
+
const mostRecent = threads[0];
|
|
68
|
+
return {
|
|
69
|
+
display: `š All caught up! Most recent: @${mostRecent.handle}\n\nUse \`vibe reply "message" --to @${mostRecent.handle}\` to continue that conversation.`,
|
|
70
|
+
actions: formatActions(actions.afterInboxCompact([{ handle: mostRecent.handle, unread: 0 }]))
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
targetHandle = unreadThread.handle;
|
|
75
|
+
threadContext = {
|
|
76
|
+
unreadCount: unreadThread.unread,
|
|
77
|
+
preview: unreadThread.lastMessage ? truncate(unreadThread.lastMessage, 50) : null
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Can't reply to yourself
|
|
82
|
+
if (targetHandle === myHandle) {
|
|
83
|
+
return { display: 'You can\'t DM yourself.' };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Route @echo messages to the echo agent
|
|
87
|
+
if (targetHandle === 'echo') {
|
|
88
|
+
const echo = require('./echo');
|
|
89
|
+
return echo.handler({ message, anonymous: false });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Truncate message if too long
|
|
93
|
+
const MAX_LENGTH = 2000;
|
|
94
|
+
const trimmed = message.trim();
|
|
95
|
+
const wasTruncated = trimmed.length > MAX_LENGTH;
|
|
96
|
+
const finalMessage = wasTruncated ? trimmed.substring(0, MAX_LENGTH) : trimmed;
|
|
97
|
+
|
|
98
|
+
// Send typing indicator (shows "typing..." to recipient)
|
|
99
|
+
store.sendTypingIndicator(myHandle, targetHandle).catch(() => {});
|
|
100
|
+
|
|
101
|
+
// Send the message
|
|
102
|
+
const result = await store.sendMessage(myHandle, targetHandle, finalMessage, 'dm', null);
|
|
103
|
+
|
|
104
|
+
// Check for errors
|
|
105
|
+
if (result && result.error) {
|
|
106
|
+
return {
|
|
107
|
+
display: `ā **Failed to send reply**\n\n${result.message}\n\n_Please try again._`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Mark the thread as read since we're replying
|
|
112
|
+
try {
|
|
113
|
+
await store.markThreadRead(myHandle, targetHandle);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
// Non-fatal - continue
|
|
116
|
+
console.warn('[reply] Failed to mark thread as read:', e.message);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Log social pattern
|
|
120
|
+
patterns.logMessageSent(targetHandle);
|
|
121
|
+
|
|
122
|
+
// Record connection if first time
|
|
123
|
+
try {
|
|
124
|
+
const hasConnected = await userProfiles.hasBeenConnected(myHandle, targetHandle);
|
|
125
|
+
if (!hasConnected) {
|
|
126
|
+
await userProfiles.recordConnection(myHandle, targetHandle, 'first_message');
|
|
127
|
+
}
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.warn('[reply] Failed to update profile connection:', e);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Track for session summary
|
|
133
|
+
trackMessage(myHandle, targetHandle, 'sent');
|
|
134
|
+
|
|
135
|
+
// Build response
|
|
136
|
+
let display = `ā Replied to **@${targetHandle}**`;
|
|
137
|
+
|
|
138
|
+
if (wasTruncated) {
|
|
139
|
+
display += ` ${warning(`truncated to ${MAX_LENGTH} chars`)}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (threadContext) {
|
|
143
|
+
display += `\n\n_${threadContext.unreadCount} message${threadContext.unreadCount > 1 ? 's' : ''} marked as read_`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Check for more unread
|
|
147
|
+
const remainingThreads = await store.getInbox(myHandle);
|
|
148
|
+
const stillUnread = remainingThreads.filter(t => t.unread > 0 && t.handle !== targetHandle);
|
|
149
|
+
|
|
150
|
+
if (stillUnread.length > 0) {
|
|
151
|
+
const nextHandle = stillUnread[0].handle;
|
|
152
|
+
const totalUnread = stillUnread.reduce((sum, t) => sum + t.unread, 0);
|
|
153
|
+
display += `\n\nš¬ ${totalUnread} more unread from ${stillUnread.map(t => `@${t.handle}`).slice(0, 3).join(', ')}`;
|
|
154
|
+
display += `\n_Say \`vibe reply "message"\` to reply to @${nextHandle}_`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Build response with actions
|
|
158
|
+
const response = { display };
|
|
159
|
+
|
|
160
|
+
// Suggest follow-up actions
|
|
161
|
+
response.actions = formatActions(actions.afterDm(targetHandle));
|
|
162
|
+
|
|
163
|
+
return response;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = { definition, handler };
|
package/tools/report.js
CHANGED
|
@@ -83,11 +83,11 @@ Nice try, but you can't report yourself.`
|
|
|
83
83
|
|
|
84
84
|
${result.error || 'Could not submit report. Please try again.'}
|
|
85
85
|
|
|
86
|
-
If the problem persists, DM @
|
|
86
|
+
If the problem persists, DM @vibe for help.`
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
const display = `## Report Submitted
|
|
91
91
|
|
|
92
92
|
**Report ID:** ${result.report_id}
|
|
93
93
|
**Reported:** @${reportedHandle}
|