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
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe onboarding โ View your onboarding checklist progress
|
|
3
|
+
*
|
|
4
|
+
* Shows the 5 onboarding tasks and their completion status.
|
|
5
|
+
* Tasks are auto-detected as you use vibe.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const config = require('../config');
|
|
9
|
+
const store = require('../store');
|
|
10
|
+
|
|
11
|
+
const definition = {
|
|
12
|
+
name: 'vibe_onboarding',
|
|
13
|
+
description: 'View your onboarding checklist progress. Shows tasks that help you get started with /vibe.',
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {},
|
|
17
|
+
required: []
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Format a single task for display
|
|
23
|
+
*/
|
|
24
|
+
function formatTask(task, index) {
|
|
25
|
+
const checkbox = task.completed ? '[x]' : '[ ]';
|
|
26
|
+
const status = task.completed ? 'โ' : ' ';
|
|
27
|
+
const completedText = task.completed && task.completedAt
|
|
28
|
+
? ` _(done ${formatTimeAgo(task.completedAt)})_`
|
|
29
|
+
: '';
|
|
30
|
+
|
|
31
|
+
return `${checkbox} **${task.title}**${completedText}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Format timestamp to relative time
|
|
36
|
+
*/
|
|
37
|
+
function formatTimeAgo(timestamp) {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
const then = new Date(timestamp).getTime();
|
|
40
|
+
const seconds = Math.floor((now - then) / 1000);
|
|
41
|
+
|
|
42
|
+
if (seconds < 60) return 'just now';
|
|
43
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
|
|
44
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
|
|
45
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Generate progress bar
|
|
50
|
+
*/
|
|
51
|
+
function generateProgressBar(completed, total) {
|
|
52
|
+
const percentage = Math.round((completed / total) * 100);
|
|
53
|
+
const filled = Math.round((completed / total) * 10);
|
|
54
|
+
const empty = 10 - filled;
|
|
55
|
+
const bar = 'โ'.repeat(filled) + 'โ'.repeat(empty);
|
|
56
|
+
return `${bar} ${percentage}%`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function handler(args) {
|
|
60
|
+
const handle = config.getHandle();
|
|
61
|
+
|
|
62
|
+
if (!handle) {
|
|
63
|
+
return {
|
|
64
|
+
display: `## Not Initialized
|
|
65
|
+
|
|
66
|
+
Run \`vibe init\` first to set your identity and start onboarding.`
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Fetch checklist status from API
|
|
71
|
+
try {
|
|
72
|
+
const response = await store.getChecklistStatus(handle);
|
|
73
|
+
|
|
74
|
+
if (!response.success) {
|
|
75
|
+
return {
|
|
76
|
+
display: `## Onboarding Checklist
|
|
77
|
+
|
|
78
|
+
_Unable to load checklist. Try again later._
|
|
79
|
+
|
|
80
|
+
Error: ${response.error || 'Unknown error'}`
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const { tasks, progress, allComplete } = response;
|
|
85
|
+
|
|
86
|
+
// Build display
|
|
87
|
+
let display = `## Onboarding Checklist
|
|
88
|
+
|
|
89
|
+
`;
|
|
90
|
+
|
|
91
|
+
// Progress bar
|
|
92
|
+
display += `**Progress:** ${generateProgressBar(progress.completed, progress.total)} (${progress.completed}/${progress.total})\n\n`;
|
|
93
|
+
|
|
94
|
+
// Celebration if complete
|
|
95
|
+
if (allComplete) {
|
|
96
|
+
display += `๐ **Congratulations!** You've completed onboarding!\n\n`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Tasks
|
|
100
|
+
display += `### Tasks\n\n`;
|
|
101
|
+
tasks.forEach((task, i) => {
|
|
102
|
+
display += formatTask(task, i) + '\n';
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Next action hints
|
|
106
|
+
if (!allComplete) {
|
|
107
|
+
display += '\n### Quick Actions\n\n';
|
|
108
|
+
|
|
109
|
+
const nextTask = tasks.find(t => !t.completed);
|
|
110
|
+
if (nextTask) {
|
|
111
|
+
switch (nextTask.id) {
|
|
112
|
+
case 'read_welcome':
|
|
113
|
+
display += 'โ Say **"check my messages"** to read @seth\'s welcome\n';
|
|
114
|
+
break;
|
|
115
|
+
case 'reply_seth':
|
|
116
|
+
display += 'โ Say **"reply to seth"** or **"dm @seth hi!"**\n';
|
|
117
|
+
break;
|
|
118
|
+
case 'find_github_friends':
|
|
119
|
+
display += 'โ Say **"find my github friends"** to see who you know! ๐ฅ\n';
|
|
120
|
+
break;
|
|
121
|
+
case 'first_ship':
|
|
122
|
+
display += 'โ Say **"ship something"** or **"I shipped X"**\n';
|
|
123
|
+
break;
|
|
124
|
+
case 'share_ship':
|
|
125
|
+
display += 'โ After shipping, click the Twitter link to share! ๐ฆ\n';
|
|
126
|
+
break;
|
|
127
|
+
case 'invite_friend':
|
|
128
|
+
display += 'โ Say **"invite a friend"** or **"share my invite link"**\n';
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { display };
|
|
135
|
+
|
|
136
|
+
} catch (e) {
|
|
137
|
+
return {
|
|
138
|
+
display: `## Onboarding Checklist
|
|
139
|
+
|
|
140
|
+
_Error loading checklist: ${e.message}_
|
|
141
|
+
|
|
142
|
+
Try running \`vibe test\` to check connectivity.`
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = { definition, handler };
|
package/tools/open.js
CHANGED
|
@@ -8,6 +8,15 @@ const memory = require('../memory');
|
|
|
8
8
|
const patterns = require('../intelligence/patterns');
|
|
9
9
|
const { formatPayload } = require('../protocol');
|
|
10
10
|
const { requireInit, normalizeHandle } = require('./_shared');
|
|
11
|
+
const { actions, formatActions } = require('./_actions');
|
|
12
|
+
|
|
13
|
+
// Truncate message for preview (first 100 chars, clean break at word)
|
|
14
|
+
function summarizeMessage(text, maxLen = 100) {
|
|
15
|
+
if (!text || text.length <= maxLen) return text;
|
|
16
|
+
const truncated = text.slice(0, maxLen);
|
|
17
|
+
const lastSpace = truncated.lastIndexOf(' ');
|
|
18
|
+
return (lastSpace > maxLen * 0.7 ? truncated.slice(0, lastSpace) : truncated) + '...';
|
|
19
|
+
}
|
|
11
20
|
|
|
12
21
|
const definition = {
|
|
13
22
|
name: 'vibe_open',
|
|
@@ -46,38 +55,152 @@ async function handler(args) {
|
|
|
46
55
|
patterns.logMessageReceived(them);
|
|
47
56
|
}
|
|
48
57
|
|
|
49
|
-
// Check if they're typing
|
|
58
|
+
// Check if they're typing (cross-platform: synced via KV)
|
|
50
59
|
let typingNotice = '';
|
|
51
60
|
try {
|
|
52
61
|
const typingUsers = await store.getTypingUsers(myHandle);
|
|
53
|
-
|
|
62
|
+
const isTyping = typingUsers.some(t => t.handle?.toLowerCase() === them.toLowerCase());
|
|
63
|
+
if (isTyping) {
|
|
54
64
|
typingNotice = `\n_@${them} is typing..._\n`;
|
|
55
65
|
}
|
|
56
66
|
} catch (e) {}
|
|
57
67
|
|
|
68
|
+
// Get their presence status (for "last seen" display)
|
|
69
|
+
let presenceStatus = '';
|
|
70
|
+
try {
|
|
71
|
+
const activeUsers = await store.getActiveUsers();
|
|
72
|
+
const theirPresence = activeUsers.find(u => u.handle?.toLowerCase() === them.toLowerCase());
|
|
73
|
+
|
|
74
|
+
if (theirPresence) {
|
|
75
|
+
if (theirPresence.status === 'active') {
|
|
76
|
+
presenceStatus = '๐ข online now';
|
|
77
|
+
} else if (theirPresence.status === 'away') {
|
|
78
|
+
const ago = store.formatTimeAgo(theirPresence.lastSeen);
|
|
79
|
+
presenceStatus = `โ away ยท last seen ${ago}`;
|
|
80
|
+
} else {
|
|
81
|
+
const ago = store.formatTimeAgo(theirPresence.lastSeen);
|
|
82
|
+
presenceStatus = `last seen ${ago}`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {}
|
|
86
|
+
|
|
58
87
|
if (thread.length === 0) {
|
|
59
|
-
|
|
60
|
-
|
|
88
|
+
const statusLine = presenceStatus ? `\n_${presenceStatus}_` : '';
|
|
89
|
+
|
|
90
|
+
// Fetch conversation starters to help break the ice
|
|
91
|
+
let starters = [];
|
|
92
|
+
let starterContext = {};
|
|
93
|
+
try {
|
|
94
|
+
const apiUrl = config.getApiUrl();
|
|
95
|
+
const promptsResponse = await fetch(`${apiUrl}/api/prompts?from=${myHandle}&to=${them}`, {
|
|
96
|
+
headers: { 'User-Agent': 'vibe-mcp-client' }
|
|
97
|
+
});
|
|
98
|
+
if (promptsResponse.ok) {
|
|
99
|
+
const data = await promptsResponse.json();
|
|
100
|
+
if (data.success && data.prompts) {
|
|
101
|
+
starters = data.prompts.slice(0, 3);
|
|
102
|
+
starterContext = data.context || {};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {}
|
|
106
|
+
|
|
107
|
+
// Build display with conversation starters
|
|
108
|
+
let emptyDisplay = `## @${them}${statusLine}
|
|
61
109
|
|
|
62
110
|
_No messages yet._${typingNotice}
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
if (starters.length > 0) {
|
|
114
|
+
emptyDisplay += `\n**๐ฌ Conversation starters:**\n`;
|
|
115
|
+
starters.forEach((starter, i) => {
|
|
116
|
+
emptyDisplay += `${i + 1}. "${starter}"\n`;
|
|
117
|
+
});
|
|
118
|
+
emptyDisplay += `\n_Copy one or say your own!_`;
|
|
119
|
+
} else {
|
|
120
|
+
emptyDisplay += `\nSay "message ${them} hello" to start`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Build response with actions for quick-send
|
|
124
|
+
const response = { display: emptyDisplay };
|
|
125
|
+
|
|
126
|
+
if (starters.length > 0) {
|
|
127
|
+
response.actions = formatActions(starters.map((starter, i) => ({
|
|
128
|
+
label: `Send #${i + 1}`,
|
|
129
|
+
action: `vibe dm @${them} ${starter}`,
|
|
130
|
+
description: starter.slice(0, 40) + (starter.length > 40 ? '...' : '')
|
|
131
|
+
})));
|
|
132
|
+
response.conversationStarters = starters;
|
|
133
|
+
response.context = starterContext;
|
|
134
|
+
}
|
|
63
135
|
|
|
64
|
-
|
|
65
|
-
};
|
|
136
|
+
return response;
|
|
66
137
|
}
|
|
67
138
|
|
|
68
|
-
//
|
|
69
|
-
const
|
|
70
|
-
|
|
139
|
+
// Get latest message from them for the summary preview
|
|
140
|
+
const latestFromThem = theirMessages.length > 0
|
|
141
|
+
? theirMessages.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0))[0]
|
|
142
|
+
: null;
|
|
143
|
+
|
|
144
|
+
// Build the preview summary line (what appears in collapsed view)
|
|
145
|
+
let display = '';
|
|
146
|
+
|
|
147
|
+
// Header with presence status
|
|
148
|
+
const statusSuffix = presenceStatus ? ` ยท _${presenceStatus}_` : '';
|
|
71
149
|
|
|
72
|
-
|
|
150
|
+
if (latestFromThem) {
|
|
151
|
+
const agentBadge = latestFromThem.isAgent ? ' ๐ค' : '';
|
|
152
|
+
const time = store.formatTimeAgo(latestFromThem.timestamp);
|
|
153
|
+
const preview = latestFromThem.body
|
|
154
|
+
? summarizeMessage(latestFromThem.body)
|
|
155
|
+
: (latestFromThem.payload ? '[attachment]' : '');
|
|
73
156
|
|
|
74
|
-
|
|
157
|
+
display = `๐ฌ @${them}${agentBadge}${statusSuffix}\n> (${time}): "${preview}"\n\n`;
|
|
158
|
+
} else {
|
|
159
|
+
// No messages from them yet - you sent first
|
|
160
|
+
display = `๐ฌ @${them}${statusSuffix}\n> _Waiting for reply..._\n\n`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Thread section - sorted newest first so their latest message appears at top
|
|
164
|
+
display += `---\n๐ Thread\n\n`;
|
|
165
|
+
|
|
166
|
+
const sortedThread = [...thread].sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
|
|
167
|
+
|
|
168
|
+
// Build a map for reply lookups
|
|
169
|
+
const messageMap = {};
|
|
170
|
+
thread.forEach(m => { messageMap[m.id] = m; });
|
|
171
|
+
|
|
172
|
+
sortedThread.forEach(m => {
|
|
75
173
|
const isMe = m.from === myHandle;
|
|
76
174
|
const agentBadge = m.isAgent && !isMe ? '๐ค ' : '';
|
|
77
175
|
const sender = isMe ? 'you' : `@${m.from}`;
|
|
78
176
|
const time = store.formatTimeAgo(m.timestamp);
|
|
79
177
|
|
|
80
|
-
|
|
178
|
+
// Status indicator for sent messages: โ sent, โโ delivered, โโ read
|
|
179
|
+
let statusIndicator = '';
|
|
180
|
+
if (isMe) {
|
|
181
|
+
if (m.status === 'read' || m.readByThem) {
|
|
182
|
+
statusIndicator = ' โโ read';
|
|
183
|
+
} else if (m.status === 'delivered' || m.delivered) {
|
|
184
|
+
statusIndicator = ' โโ';
|
|
185
|
+
} else {
|
|
186
|
+
statusIndicator = ' โ';
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
display += `${agentBadge}**${sender}** โ _${time}${statusIndicator}_\n`;
|
|
191
|
+
|
|
192
|
+
// Show reply context if this is a threaded reply
|
|
193
|
+
if (m.reply_to_id || m.replyToId) {
|
|
194
|
+
const replyToId = m.reply_to_id || m.replyToId;
|
|
195
|
+
const originalMsg = messageMap[replyToId];
|
|
196
|
+
if (originalMsg) {
|
|
197
|
+
const originalSender = originalMsg.from === myHandle ? 'you' : `@${originalMsg.from}`;
|
|
198
|
+
const preview = (originalMsg.body || '').substring(0, 50);
|
|
199
|
+
display += `> โฉ replying to ${originalSender}: "${preview}${preview.length >= 50 ? '...' : ''}"\n`;
|
|
200
|
+
} else {
|
|
201
|
+
display += `> โฉ _replying to earlier message_\n`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
81
204
|
|
|
82
205
|
// Show text if present
|
|
83
206
|
if (m.body) {
|
|
@@ -114,6 +237,14 @@ Say "message ${them} hello" to start`
|
|
|
114
237
|
response.reason = 'long_thread';
|
|
115
238
|
}
|
|
116
239
|
|
|
240
|
+
// Add smart reply actions based on their last message
|
|
241
|
+
if (latestFromThem && latestFromThem.body) {
|
|
242
|
+
response.actions = formatActions(actions.afterOpenThread(them, latestFromThem.body));
|
|
243
|
+
} else {
|
|
244
|
+
// No message from them yet - offer generic options
|
|
245
|
+
response.actions = formatActions(actions.afterOpenThread(them, ''));
|
|
246
|
+
}
|
|
247
|
+
|
|
117
248
|
return response;
|
|
118
249
|
}
|
|
119
250
|
|
package/tools/party-game.js
CHANGED
|
@@ -15,7 +15,7 @@ const werewolf = require('../games/werewolf');
|
|
|
15
15
|
|
|
16
16
|
// Post game results to board
|
|
17
17
|
async function postPartyGameResult(game, message) {
|
|
18
|
-
const API_URL = process.env.VIBE_API_URL || 'https://slashvibe.dev';
|
|
18
|
+
const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
21
|
await fetch(`${API_URL}/api/board`, {
|
|
@@ -103,7 +103,7 @@ async function handler(args) {
|
|
|
103
103
|
const gameKey = getGameKey(game, room || myHandle);
|
|
104
104
|
|
|
105
105
|
// Get or create game state
|
|
106
|
-
|
|
106
|
+
const gameState = activeGames[gameKey];
|
|
107
107
|
|
|
108
108
|
if (game === 'twotruths') {
|
|
109
109
|
return handleTwoTruths(args, myHandle, gameKey, gameState);
|
package/tools/plan.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe_plan โ Create a plan and request approval
|
|
3
|
+
*
|
|
4
|
+
* Plans are structured proposals that can be sent for async approval.
|
|
5
|
+
* They're artifacts with an approval workflow built in.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const config = require('../config');
|
|
9
|
+
const store = require('../store');
|
|
10
|
+
const { requireInit, normalizeHandle } = require('./_shared');
|
|
11
|
+
|
|
12
|
+
const definition = {
|
|
13
|
+
name: 'vibe_plan',
|
|
14
|
+
description: 'Create a plan and request approval from collaborators. Plans are structured proposals with summary, problem, changes, and impact.',
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
title: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'Plan title (e.g., "Refactor auth system", "CLAUDE.md cleanup")'
|
|
21
|
+
},
|
|
22
|
+
summary: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'One-line summary of what this plan does'
|
|
25
|
+
},
|
|
26
|
+
problem: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'What problem does this solve? (optional)'
|
|
29
|
+
},
|
|
30
|
+
changes: {
|
|
31
|
+
type: 'array',
|
|
32
|
+
items: { type: 'string' },
|
|
33
|
+
description: 'List of proposed changes'
|
|
34
|
+
},
|
|
35
|
+
impact: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'What will be affected by this change? (optional)'
|
|
38
|
+
},
|
|
39
|
+
request_approval_from: {
|
|
40
|
+
type: 'array',
|
|
41
|
+
items: { type: 'string' },
|
|
42
|
+
description: 'Handles to request approval from (e.g., ["@brightseth", "@alice"])'
|
|
43
|
+
},
|
|
44
|
+
requires: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
enum: ['any', 'all'],
|
|
47
|
+
description: 'Require approval from any one person (default) or all of them'
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
required: ['title', 'summary', 'changes', 'request_approval_from']
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Generate artifact ID and slug
|
|
55
|
+
function generateArtifactId() {
|
|
56
|
+
return `plan_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function generateSlug(title) {
|
|
60
|
+
return title
|
|
61
|
+
.toLowerCase()
|
|
62
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
63
|
+
.replace(/^-|-$/g, '')
|
|
64
|
+
.substring(0, 60);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function handler(args) {
|
|
68
|
+
const initCheck = requireInit();
|
|
69
|
+
if (initCheck) return initCheck;
|
|
70
|
+
|
|
71
|
+
const {
|
|
72
|
+
title,
|
|
73
|
+
summary,
|
|
74
|
+
problem,
|
|
75
|
+
changes,
|
|
76
|
+
impact,
|
|
77
|
+
request_approval_from,
|
|
78
|
+
requires = 'any'
|
|
79
|
+
} = args;
|
|
80
|
+
|
|
81
|
+
const creator = config.getHandle();
|
|
82
|
+
|
|
83
|
+
// Validate approvers
|
|
84
|
+
if (!request_approval_from || request_approval_from.length === 0) {
|
|
85
|
+
return {
|
|
86
|
+
display: 'โ Must specify at least one approver in request_approval_from'
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Normalize approver handles
|
|
91
|
+
const approvers = request_approval_from.map(h => normalizeHandle(h));
|
|
92
|
+
|
|
93
|
+
// Build content blocks for the plan
|
|
94
|
+
const blocks = [
|
|
95
|
+
{ type: 'summary', data: { text: summary } }
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
if (problem) {
|
|
99
|
+
blocks.push({ type: 'problem', data: { text: problem } });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
blocks.push({
|
|
103
|
+
type: 'proposed_changes',
|
|
104
|
+
data: { items: changes }
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (impact) {
|
|
108
|
+
blocks.push({ type: 'impact', data: { text: impact } });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Build the plan artifact
|
|
112
|
+
const planId = generateArtifactId();
|
|
113
|
+
const slug = generateSlug(title);
|
|
114
|
+
|
|
115
|
+
// Build audience list (creator + all approvers)
|
|
116
|
+
const audience = new Set([creator, ...approvers]);
|
|
117
|
+
|
|
118
|
+
const plan = {
|
|
119
|
+
id: planId,
|
|
120
|
+
slug,
|
|
121
|
+
title,
|
|
122
|
+
template: 'plan',
|
|
123
|
+
content: { blocks },
|
|
124
|
+
|
|
125
|
+
// Social metadata
|
|
126
|
+
created_by: creator,
|
|
127
|
+
created_for: approvers[0], // Primary approver
|
|
128
|
+
thread_id: null,
|
|
129
|
+
|
|
130
|
+
// Privacy - plans are unlisted by default
|
|
131
|
+
visibility: 'unlisted',
|
|
132
|
+
audience: Array.from(audience),
|
|
133
|
+
|
|
134
|
+
// Provenance
|
|
135
|
+
provenance: {
|
|
136
|
+
source_type: 'manual',
|
|
137
|
+
personalized_for: null,
|
|
138
|
+
notes: null
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
// Lifecycle
|
|
142
|
+
created_at: new Date().toISOString(),
|
|
143
|
+
updated_at: new Date().toISOString(),
|
|
144
|
+
expires_at: null,
|
|
145
|
+
|
|
146
|
+
// Evolution
|
|
147
|
+
revision: 1,
|
|
148
|
+
forked_from: null,
|
|
149
|
+
|
|
150
|
+
// Approval workflow
|
|
151
|
+
approval: {
|
|
152
|
+
status: 'pending',
|
|
153
|
+
requested_from: approvers,
|
|
154
|
+
requires,
|
|
155
|
+
responses: [],
|
|
156
|
+
resolved_at: null
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Store the plan
|
|
161
|
+
const storeResult = await store.createArtifact(plan);
|
|
162
|
+
|
|
163
|
+
if (!storeResult.success) {
|
|
164
|
+
return {
|
|
165
|
+
display: `โ Failed to create plan: ${storeResult.error}`
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const planUrl = `https://slashvibe.dev/a/${slug}`;
|
|
170
|
+
|
|
171
|
+
// Send DM to each approver
|
|
172
|
+
const dmResults = [];
|
|
173
|
+
for (const approver of approvers) {
|
|
174
|
+
try {
|
|
175
|
+
const dmResult = await store.sendMessage(creator, approver,
|
|
176
|
+
`๐ **Plan: ${title}**\n\n` +
|
|
177
|
+
`**Summary:** ${summary}\n\n` +
|
|
178
|
+
(problem ? `**Problem:** ${problem}\n\n` : '') +
|
|
179
|
+
`**Changes:**\n${changes.map(c => `โข ${c}`).join('\n')}\n\n` +
|
|
180
|
+
(impact ? `**Impact:** ${impact}\n\n` : '') +
|
|
181
|
+
`---\n` +
|
|
182
|
+
`๐ Approve ยท ๐ Request changes ยท โ Reject\n` +
|
|
183
|
+
`Use: \`vibe approve ${slug}\` or \`vibe approve ${slug} --reject\``,
|
|
184
|
+
{
|
|
185
|
+
type: 'plan_card',
|
|
186
|
+
plan_id: planId,
|
|
187
|
+
plan_slug: slug,
|
|
188
|
+
plan_url: planUrl
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
dmResults.push({ approver, success: dmResult.success });
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error(`[PLAN] Failed to DM ${approver}:`, error);
|
|
194
|
+
dmResults.push({ approver, success: false, error: error.message });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Build response
|
|
199
|
+
const successfulDMs = dmResults.filter(r => r.success).length;
|
|
200
|
+
const approverList = approvers.map(a => `@${a}`).join(', ');
|
|
201
|
+
|
|
202
|
+
let display = `๐ **Plan created: ${title}**\n\n`;
|
|
203
|
+
display += `**Status:** โณ Pending approval\n`;
|
|
204
|
+
display += `**Requested from:** ${approverList}\n`;
|
|
205
|
+
display += `**Requires:** ${requires === 'all' ? 'All approvers' : 'Any approver'}\n\n`;
|
|
206
|
+
|
|
207
|
+
if (successfulDMs === approvers.length) {
|
|
208
|
+
display += `โ Sent to ${successfulDMs} approver${successfulDMs > 1 ? 's' : ''}\n`;
|
|
209
|
+
} else {
|
|
210
|
+
display += `โ ๏ธ Sent to ${successfulDMs}/${approvers.length} approvers\n`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
display += `\n**Plan ID:** \`${slug}\``;
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
display,
|
|
217
|
+
plan_id: planId,
|
|
218
|
+
plan_slug: slug,
|
|
219
|
+
plan_url: planUrl,
|
|
220
|
+
approval_status: 'pending',
|
|
221
|
+
approvers
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
module.exports = { definition, handler };
|
package/tools/presence-agent.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const config = require('../config');
|
|
9
|
+
const analytics = require('../analytics');
|
|
9
10
|
|
|
10
11
|
const definition = {
|
|
11
12
|
name: 'vibe_presence_agent',
|
|
@@ -81,6 +82,9 @@ ${running ? '**Commands:**\n- `mute vibe for 1 hour` - Pause alerts\n- `stop pre
|
|
|
81
82
|
config.set('presenceAgentRunning', true);
|
|
82
83
|
config.set('presenceAgentStartedAt', Date.now());
|
|
83
84
|
|
|
85
|
+
// Track lurk mode enabled for retention analytics
|
|
86
|
+
analytics.trackLurkMode(true);
|
|
87
|
+
|
|
84
88
|
return {
|
|
85
89
|
display: `## ๐ข Presence Monitor Started
|
|
86
90
|
|
|
@@ -141,6 +145,9 @@ You can keep working - I'll alert you when something important happens.`,
|
|
|
141
145
|
config.set('presenceAgentRunning', false);
|
|
142
146
|
config.set('presenceAgentStartedAt', null);
|
|
143
147
|
|
|
148
|
+
// Track lurk mode disabled for retention analytics
|
|
149
|
+
analytics.trackLurkMode(false);
|
|
150
|
+
|
|
144
151
|
return {
|
|
145
152
|
display: `## โช Presence Monitor Stopped${durationText}
|
|
146
153
|
|