slashvibe-mcp 0.2.8 → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -58
- package/analytics.js +107 -0
- package/auth-store.js +148 -0
- package/auto-update.js +130 -0
- package/bridges/bridge-monitor.js +388 -0
- package/bridges/discord-bot.js +431 -0
- package/bridges/farcaster.js +299 -0
- package/bridges/telegram.js +261 -0
- package/bridges/webhook-health.js +420 -0
- package/bridges/webhook-server.js +437 -0
- package/bridges/whatsapp.js +441 -0
- package/bridges/x-webhook.js +423 -0
- package/config.js +27 -15
- package/games/arcade.js +406 -0
- package/games/chess.js +451 -0
- package/games/colorguess.js +343 -0
- package/games/crossword-words.js +171 -0
- package/games/crossword.js +461 -0
- package/games/drawing.js +347 -0
- package/games/gameroulette.js +300 -0
- package/games/gamerouter.js +336 -0
- package/games/gamestatus.js +337 -0
- package/games/guessnumber.js +209 -0
- package/games/hangman.js +279 -0
- package/games/memory.js +338 -0
- package/games/multiplayer-tictactoe.js +389 -0
- package/games/pixelart.js +399 -0
- package/games/quickduel.js +354 -0
- package/games/riddle.js +371 -0
- package/games/rockpaperscissors.js +291 -0
- package/games/snake.js +406 -0
- package/games/storybuilder.js +343 -0
- package/games/tictactoe.js +345 -0
- package/games/twentyquestions.js +286 -0
- package/games/twotruths.js +207 -0
- package/games/werewolf.js +508 -0
- package/games/wordassociation.js +247 -0
- package/games/wordchain.js +135 -0
- package/index.js +77 -53
- package/intelligence/index.js +9 -2
- package/intelligence/interests.js +369 -0
- package/notification-emitter.js +77 -0
- package/notify.js +5 -1
- package/package.json +18 -6
- package/prompts.js +1 -1
- package/protocol/index.js +73 -0
- package/setup.js +402 -0
- package/store/api.js +436 -211
- package/store/profiles.js +160 -12
- package/tools/_actions.js +362 -21
- package/tools/_discovery.js +119 -26
- package/tools/_shared/index.js +64 -0
- package/tools/_shared.js +234 -0
- package/tools/_work-context.js +338 -0
- package/tools/_work-context.manual-test.js +199 -0
- package/tools/_work-context.test.js +260 -0
- package/tools/activity.js +220 -0
- package/tools/analytics.js +191 -0
- package/tools/approve.js +197 -0
- package/tools/artifact-create.js +14 -3
- package/tools/artifacts-price.js +107 -0
- package/tools/available.js +120 -0
- package/tools/broadcast.js +286 -0
- package/tools/chat.js +202 -0
- package/tools/collaborative-drawing.js +1 -1
- package/tools/connection-status.js +178 -0
- package/tools/discover.js +350 -34
- package/tools/dm.js +80 -8
- package/tools/earnings.js +126 -0
- package/tools/feed.js +35 -4
- package/tools/follow.js +224 -0
- package/tools/friends.js +207 -0
- package/tools/gig-browse.js +206 -0
- package/tools/gig-complete.js +144 -0
- package/tools/help.js +3 -3
- package/tools/idea.js +9 -2
- package/tools/inbox.js +289 -105
- package/tools/init.js +106 -27
- package/tools/invite.js +15 -4
- package/tools/migrate.js +3 -3
- package/tools/multiplayer-game.js +1 -1
- package/tools/onboarding.js +7 -7
- package/tools/open.js +143 -12
- package/tools/party-game.js +1 -1
- package/tools/plan.js +225 -0
- package/tools/proof-of-work.js +144 -0
- package/tools/reply.js +166 -0
- package/tools/report.js +1 -1
- package/tools/request.js +17 -3
- package/tools/schedule.js +367 -0
- package/tools/search-messages.js +123 -0
- package/tools/session.js +420 -0
- package/tools/session_price.js +128 -0
- package/tools/settings.js +90 -2
- package/tools/ship.js +30 -7
- package/tools/smart-check.js +201 -0
- package/tools/start.js +147 -12
- package/tools/status.js +53 -6
- package/tools/stuck.js +297 -0
- package/tools/subscribe.js +148 -0
- package/tools/subscriptions.js +134 -0
- package/tools/suggest-tags.js +6 -8
- package/tools/tag-suggestions.js +1 -1
- package/tools/tip.js +150 -77
- package/tools/token.js +4 -4
- package/tools/update.js +1 -1
- package/tools/wallet.js +221 -79
- package/tools/watch.js +157 -0
- package/tools/who.js +30 -1
- package/tools/withdraw.js +145 -0
- package/tools/work-summary.js +96 -0
- package/version.json +10 -8
- package/LICENSE +0 -21
- package/store/sqlite.js +0 -347
- /package/tools/{auto-suggest-connections.js → _deprecated/auto-suggest-connections.js} +0 -0
- /package/tools/{away.js → _deprecated/away.js} +0 -0
- /package/tools/{back.js → _deprecated/back.js} +0 -0
- /package/tools/{bootstrap-skills.js → _deprecated/bootstrap-skills.js} +0 -0
- /package/tools/{bridge-dashboard.js → _deprecated/bridge-dashboard.js} +0 -0
- /package/tools/{bridge-health.js → _deprecated/bridge-health.js} +0 -0
- /package/tools/{bridge-live.js → _deprecated/bridge-live.js} +0 -0
- /package/tools/{bridges.js → _deprecated/bridges.js} +0 -0
- /package/tools/{colorguess.js → _deprecated/colorguess.js} +0 -0
- /package/tools/{discover-insights.js → _deprecated/discover-insights.js} +0 -0
- /package/tools/{discover-momentum.js → _deprecated/discover-momentum.js} +0 -0
- /package/tools/{discovery-analytics.js → _deprecated/discovery-analytics.js} +0 -0
- /package/tools/{discovery-auto-suggest.js → _deprecated/discovery-auto-suggest.js} +0 -0
- /package/tools/{discovery-bootstrap.js → _deprecated/discovery-bootstrap.js} +0 -0
- /package/tools/{discovery-daily.js → _deprecated/discovery-daily.js} +0 -0
- /package/tools/{discovery-dashboard.js → _deprecated/discovery-dashboard.js} +0 -0
- /package/tools/{discovery-digest.js → _deprecated/discovery-digest.js} +0 -0
- /package/tools/{discovery-hub.js → _deprecated/discovery-hub.js} +0 -0
- /package/tools/{discovery-insights.js → _deprecated/discovery-insights.js} +0 -0
- /package/tools/{discovery-momentum.js → _deprecated/discovery-momentum.js} +0 -0
- /package/tools/{discovery-monitor.js → _deprecated/discovery-monitor.js} +0 -0
- /package/tools/{discovery-proactive.js → _deprecated/discovery-proactive.js} +0 -0
- /package/tools/{draw.js → _deprecated/draw.js} +0 -0
- /package/tools/{farcaster.js → _deprecated/farcaster.js} +0 -0
- /package/tools/{forget.js → _deprecated/forget.js} +0 -0
- /package/tools/{games-catalog.js → _deprecated/games-catalog.js} +0 -0
- /package/tools/{games.js → _deprecated/games.js} +0 -0
- /package/tools/{guessnumber.js → _deprecated/guessnumber.js} +0 -0
- /package/tools/{hangman.js → _deprecated/hangman.js} +0 -0
- /package/tools/{multiplayer-tictactoe.js → _deprecated/multiplayer-tictactoe.js} +0 -0
- /package/tools/{mute.js → _deprecated/mute.js} +0 -0
- /package/tools/{recall.js → _deprecated/recall.js} +0 -0
- /package/tools/{remember.js → _deprecated/remember.js} +0 -0
- /package/tools/{riddle.js → _deprecated/riddle.js} +0 -0
- /package/tools/{run-bootstrap.js → _deprecated/run-bootstrap.js} +0 -0
- /package/tools/{skills-analytics.js → _deprecated/skills-analytics.js} +0 -0
- /package/tools/{skills-bootstrap.js → _deprecated/skills-bootstrap.js} +0 -0
- /package/tools/{skills-dashboard.js → _deprecated/skills-dashboard.js} +0 -0
- /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
- /package/tools/{skills.js → _deprecated/skills.js} +0 -0
- /package/tools/{smart-intro.js → _deprecated/smart-intro.js} +0 -0
- /package/tools/{storybuilder.js → _deprecated/storybuilder.js} +0 -0
- /package/tools/{telegram-bot.js → _deprecated/telegram-bot.js} +0 -0
- /package/tools/{telegram-setup.js → _deprecated/telegram-setup.js} +0 -0
- /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
- /package/tools/{twentyquestions.js → _deprecated/twentyquestions.js} +0 -0
- /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
package/tools/invite.js
CHANGED
|
@@ -203,14 +203,25 @@ _Copy the link above and send it._`
|
|
|
203
203
|
const genNote = justGenerated ? ' (just generated)' : '';
|
|
204
204
|
const randomMsg = INVITE_MESSAGES[Math.floor(Math.random() * INVITE_MESSAGES.length)];
|
|
205
205
|
|
|
206
|
+
// Build social share URLs
|
|
207
|
+
const tweetText = encodeURIComponent(`${randomMsg}\n\nhttps://${shareUrl}`);
|
|
208
|
+
const twitterUrl = `https://twitter.com/intent/tweet?text=${tweetText}`;
|
|
209
|
+
|
|
206
210
|
return {
|
|
207
211
|
display: `## Quick Invite${genNote}
|
|
208
212
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
213
|
+
**Copy this:**
|
|
214
|
+
\`\`\`
|
|
215
|
+
${randomMsg}
|
|
216
|
+
|
|
217
|
+
https://${shareUrl}
|
|
218
|
+
\`\`\`
|
|
219
|
+
|
|
220
|
+
**Or share directly:**
|
|
221
|
+
- [Tweet it](${twitterUrl})
|
|
222
|
+
- Slack/Discord: Just paste the link above
|
|
212
223
|
|
|
213
|
-
|
|
224
|
+
${shareCode ? `_When they join, you both get bonus codes._` : ''}`
|
|
214
225
|
};
|
|
215
226
|
}
|
|
216
227
|
|
package/tools/migrate.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* vibe migrate — Migrate existing handle to GitHub auth
|
|
3
3
|
*
|
|
4
4
|
* For users who have existing handles with local keypairs,
|
|
5
|
-
* this command helps them migrate to
|
|
5
|
+
* this command helps them migrate to GitHub OAuth auth.
|
|
6
6
|
*
|
|
7
7
|
* Flow:
|
|
8
8
|
* 1. Check if user has existing handle and keys
|
|
@@ -36,8 +36,8 @@ If you're new, use \`vibe init @yourhandle "what you're building"\` instead.`
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// Check if already using
|
|
40
|
-
if (config.
|
|
39
|
+
// Check if already using GitHub auth
|
|
40
|
+
if (config.hasOAuth()) {
|
|
41
41
|
return {
|
|
42
42
|
display: `✅ **Already using GitHub auth**
|
|
43
43
|
|
package/tools/onboarding.js
CHANGED
|
@@ -110,23 +110,23 @@ Error: ${response.error || 'Unknown error'}`
|
|
|
110
110
|
if (nextTask) {
|
|
111
111
|
switch (nextTask.id) {
|
|
112
112
|
case 'read_welcome':
|
|
113
|
-
display += '→ Say **"check my messages"** to read @
|
|
113
|
+
display += '→ Say **"check my messages"** to read @seth\'s welcome\n';
|
|
114
114
|
break;
|
|
115
115
|
case 'reply_seth':
|
|
116
|
-
display += '→ Say **"reply to
|
|
116
|
+
display += '→ Say **"reply to seth"** or **"dm @seth hi!"**\n';
|
|
117
117
|
break;
|
|
118
|
-
case '
|
|
119
|
-
display += '→ Say **"
|
|
118
|
+
case 'find_github_friends':
|
|
119
|
+
display += '→ Say **"find my github friends"** to see who you know! 🔥\n';
|
|
120
120
|
break;
|
|
121
121
|
case 'first_ship':
|
|
122
122
|
display += '→ Say **"ship something"** or **"I shipped X"**\n';
|
|
123
123
|
break;
|
|
124
|
+
case 'share_ship':
|
|
125
|
+
display += '→ After shipping, click the Twitter link to share! 🐦\n';
|
|
126
|
+
break;
|
|
124
127
|
case 'invite_friend':
|
|
125
128
|
display += '→ Say **"invite a friend"** or **"share my invite link"**\n';
|
|
126
129
|
break;
|
|
127
|
-
case 'leave_feedback':
|
|
128
|
-
display += '→ Say **"echo feedback about X"** or **"give feedback"**\n';
|
|
129
|
-
break;
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
}
|
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
|
@@ -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 };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe_proof_of_work - Get unified proof-of-work summary for a user
|
|
3
|
+
*
|
|
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
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const config = require('../config');
|
|
12
|
+
const { requireInit, normalizeHandle, displayHandle, header, divider, emptyState, formatTimeAgo } = require('./_shared');
|
|
13
|
+
const { actions, formatActions } = require('./_actions');
|
|
14
|
+
|
|
15
|
+
const BASE_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
|
|
16
|
+
|
|
17
|
+
const definition = {
|
|
18
|
+
name: 'vibe_proof_of_work',
|
|
19
|
+
description: 'Get proof-of-work summary for a user. Shows artifacts, sessions, gigs, vibe score, and proven skills.',
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
handle: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'User handle to look up (default: yourself)'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
async function handler(args) {
|
|
32
|
+
const initCheck = requireInit();
|
|
33
|
+
if (initCheck) return initCheck;
|
|
34
|
+
|
|
35
|
+
const targetHandle = normalizeHandle(args.handle) || config.getHandle();
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const response = await fetch(`${BASE_URL}/api/proof-of-work?handle=${encodeURIComponent(targetHandle)}`);
|
|
39
|
+
const data = await response.json();
|
|
40
|
+
|
|
41
|
+
if (!data.success) {
|
|
42
|
+
return { display: `❌ ${data.error || 'Failed to fetch proof-of-work'}` };
|
|
43
|
+
}
|
|
44
|
+
|
|
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
|
+
}
|
|
79
|
+
}
|
|
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';
|
|
98
|
+
}
|
|
99
|
+
|
|
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`;
|
|
109
|
+
}
|
|
110
|
+
|
|
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
|
+
}
|
|
116
|
+
|
|
117
|
+
// Build result with tip actions
|
|
118
|
+
const result = { display };
|
|
119
|
+
result.actions = formatActions(actions.afterProofOfWork(targetHandle, isSelf));
|
|
120
|
+
|
|
121
|
+
return result;
|
|
122
|
+
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.error('[proof-of-work] Error:', e);
|
|
125
|
+
return { display: `❌ Failed to fetch proof-of-work: ${e.message}` };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
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
|
+
|
|
144
|
+
module.exports = { definition, handler };
|