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/tip.js
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* vibe_tip - Send
|
|
2
|
+
* vibe_tip - Send an instant on-chain USDC tip
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Real crypto tips with instant settlement on Base Sepolia.
|
|
5
|
+
* No fees - creator gets 100% of the tip.
|
|
6
|
+
* Gas is sponsored by /vibe.
|
|
5
7
|
*
|
|
6
8
|
* Examples:
|
|
7
9
|
* - "tip @alice $5 for helping debug"
|
|
8
|
-
* - "send @bob $
|
|
9
|
-
* - "vibe tip @charlie
|
|
10
|
+
* - "send @bob $1 thanks for the intro"
|
|
11
|
+
* - "vibe tip @charlie 10"
|
|
12
|
+
*
|
|
13
|
+
* Preset amounts: $1 (default), $5, $10
|
|
10
14
|
*/
|
|
11
15
|
|
|
12
|
-
const
|
|
16
|
+
const config = require('../config');
|
|
17
|
+
const { requireInit, normalizeHandle, displayHandle } = require('./_shared');
|
|
18
|
+
const { actions, formatActions } = require('./_actions');
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
// Preset tip amounts in cents
|
|
21
|
+
const TIP_PRESETS = {
|
|
22
|
+
1: 100, // $1
|
|
23
|
+
5: 500, // $5
|
|
24
|
+
10: 1000 // $10
|
|
25
|
+
};
|
|
17
26
|
|
|
27
|
+
const definition = {
|
|
28
|
+
name: 'vibe_tip',
|
|
29
|
+
description: 'Send an instant on-chain USDC tip. 0% fee - creator gets 100%. Gas is sponsored. Amounts: $1 (default), $5, $10.',
|
|
18
30
|
inputSchema: {
|
|
19
31
|
type: 'object',
|
|
20
32
|
properties: {
|
|
@@ -24,97 +36,158 @@ module.exports = {
|
|
|
24
36
|
},
|
|
25
37
|
amount: {
|
|
26
38
|
type: 'number',
|
|
27
|
-
description: 'Amount in USD (
|
|
39
|
+
description: 'Amount in USD (1, 5, or 10). Default: 1'
|
|
28
40
|
},
|
|
29
41
|
message: {
|
|
30
42
|
type: 'string',
|
|
31
43
|
description: 'Optional message to include with the tip'
|
|
44
|
+
},
|
|
45
|
+
context: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
description: 'Optional context (type: dm_reply|ship|profile|gig_bonus, reference_id)',
|
|
48
|
+
properties: {
|
|
49
|
+
type: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
enum: ['dm_reply', 'ship', 'profile', 'gig_bonus']
|
|
52
|
+
},
|
|
53
|
+
reference_id: {
|
|
54
|
+
type: 'string'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
32
57
|
}
|
|
33
58
|
},
|
|
34
|
-
required: ['to'
|
|
35
|
-
}
|
|
59
|
+
required: ['to']
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
async function handler(args) {
|
|
64
|
+
const initCheck = requireInit();
|
|
65
|
+
if (initCheck) return initCheck;
|
|
66
|
+
|
|
67
|
+
const { to, message, context } = args;
|
|
68
|
+
let { amount = 1 } = args;
|
|
69
|
+
|
|
70
|
+
const myHandle = config.getHandle();
|
|
71
|
+
const token = config.getToken();
|
|
72
|
+
|
|
73
|
+
if (!token) {
|
|
74
|
+
return {
|
|
75
|
+
display: 'ā **Not authenticated**\n\nRun `vibe init` to authenticate with GitHub first.'
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Normalize recipient handle
|
|
80
|
+
const recipient = normalizeHandle(to);
|
|
81
|
+
if (!recipient) {
|
|
82
|
+
return {
|
|
83
|
+
display: 'ā **Missing recipient**\n\nPlease specify who to tip (e.g., `tip @alice 5`).'
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Validate and normalize amount to preset
|
|
88
|
+
amount = parseFloat(amount);
|
|
89
|
+
if (isNaN(amount) || amount <= 0) {
|
|
90
|
+
amount = 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Round to nearest preset
|
|
94
|
+
let amountCents;
|
|
95
|
+
if (amount >= 7.5) {
|
|
96
|
+
amountCents = TIP_PRESETS[10];
|
|
97
|
+
amount = 10;
|
|
98
|
+
} else if (amount >= 3) {
|
|
99
|
+
amountCents = TIP_PRESETS[5];
|
|
100
|
+
amount = 5;
|
|
101
|
+
} else {
|
|
102
|
+
amountCents = TIP_PRESETS[1];
|
|
103
|
+
amount = 1;
|
|
104
|
+
}
|
|
36
105
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const from = context.handle;
|
|
106
|
+
// Call instant tip API
|
|
107
|
+
const apiUrl = config.getApiUrl();
|
|
40
108
|
|
|
41
|
-
|
|
109
|
+
// Generate idempotency key to prevent duplicate tips from retries
|
|
110
|
+
// Key is based on: sender + recipient + amount + timestamp (1-minute bucket)
|
|
111
|
+
const timeBucket = Math.floor(Date.now() / 60000); // 1-minute buckets
|
|
112
|
+
const idempotencyKey = `${myHandle}:${recipient}:${amountCents}:${timeBucket}`;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const response = await fetch(`${apiUrl}/api/tips/instant`, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers: {
|
|
118
|
+
'Content-Type': 'application/json',
|
|
119
|
+
'Authorization': `Bearer ${token}`,
|
|
120
|
+
'Idempotency-Key': idempotencyKey
|
|
121
|
+
},
|
|
122
|
+
body: JSON.stringify({
|
|
123
|
+
to: recipient,
|
|
124
|
+
amount_cents: amountCents,
|
|
125
|
+
message,
|
|
126
|
+
context
|
|
127
|
+
})
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const result = await response.json();
|
|
131
|
+
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
// Handle specific error cases
|
|
134
|
+
if (result.error === 'insufficient_balance') {
|
|
42
135
|
return {
|
|
43
|
-
|
|
44
|
-
|
|
136
|
+
display: `ā **Insufficient USDC balance**\n\nYou need USDC to tip. Your balance: $${result.balance_usdc}\n\n` +
|
|
137
|
+
`Get USDC at [bridge.base.org](${result.help_url})`
|
|
45
138
|
};
|
|
46
139
|
}
|
|
47
140
|
|
|
48
|
-
|
|
49
|
-
if (amount <= 0 || amount > 100) {
|
|
141
|
+
if (result.error === 'recipient_no_wallet') {
|
|
50
142
|
return {
|
|
51
|
-
|
|
52
|
-
|
|
143
|
+
display: `ā **${displayHandle(recipient)} can't receive tips yet**\n\n` +
|
|
144
|
+
`They need to complete their wallet setup first.`
|
|
53
145
|
};
|
|
54
146
|
}
|
|
55
147
|
|
|
56
|
-
|
|
57
|
-
const apiUrl = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
|
|
58
|
-
|
|
59
|
-
const response = await fetch(`${apiUrl}/api/payments/tip`, {
|
|
60
|
-
method: 'POST',
|
|
61
|
-
headers: {
|
|
62
|
-
'Content-Type': 'application/json',
|
|
63
|
-
'Authorization': `Bearer ${context.token}`
|
|
64
|
-
},
|
|
65
|
-
body: JSON.stringify({
|
|
66
|
-
from,
|
|
67
|
-
to,
|
|
68
|
-
amount,
|
|
69
|
-
message
|
|
70
|
-
})
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const result = await response.json();
|
|
74
|
-
|
|
75
|
-
if (!response.ok) {
|
|
148
|
+
if (result.error === 'rate_limited') {
|
|
76
149
|
return {
|
|
77
|
-
|
|
78
|
-
error: result.error || 'Tip failed',
|
|
79
|
-
details: result.details
|
|
150
|
+
display: `ā³ **Rate limit reached**\n\n${result.message}\n\nTry again in ${Math.ceil(result.reset_in_seconds / 60)} minutes.`
|
|
80
151
|
};
|
|
81
152
|
}
|
|
82
153
|
|
|
83
|
-
// Format success message
|
|
84
|
-
const fee = result.fee || (amount * 0.025);
|
|
85
|
-
const net = amount - fee;
|
|
86
|
-
|
|
87
154
|
return {
|
|
88
|
-
|
|
89
|
-
message: `š° Tipped ${to} $${amount}`,
|
|
90
|
-
details: {
|
|
91
|
-
recipient: to,
|
|
92
|
-
amount: `$${amount}`,
|
|
93
|
-
fee: `$${fee.toFixed(2)} (2.5%)`,
|
|
94
|
-
net_to_recipient: `$${net.toFixed(2)}`,
|
|
95
|
-
tx_hash: result.tx_hash,
|
|
96
|
-
status: result.status,
|
|
97
|
-
explorer_url: `https://sepolia.basescan.org/tx/${result.tx_hash}`
|
|
98
|
-
},
|
|
99
|
-
formatted: `
|
|
100
|
-
ā Tip sent!
|
|
101
|
-
To: ${to}
|
|
102
|
-
Amount: $${amount}
|
|
103
|
-
Fee: $${fee.toFixed(2)}
|
|
104
|
-
Net: $${net.toFixed(2)}
|
|
105
|
-
${message ? `Message: "${message}"` : ''}
|
|
106
|
-
|
|
107
|
-
Tx: ${result.tx_hash?.substring(0, 10)}...
|
|
108
|
-
Status: ${result.status}
|
|
109
|
-
`.trim()
|
|
155
|
+
display: `ā **Tip failed**\n\n${result.message || 'Please try again.'}`
|
|
110
156
|
};
|
|
157
|
+
}
|
|
111
158
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
159
|
+
// Success!
|
|
160
|
+
let display = `ā
**Tipped ${displayHandle(recipient)} $${amount} USDC**\n\n`;
|
|
161
|
+
|
|
162
|
+
display += `š° Amount: $${amount}.00\n`;
|
|
163
|
+
display += `š¤ To: ${displayHandle(recipient)}\n`;
|
|
164
|
+
display += `š„ Fee: $0.00 (0%)\n`;
|
|
165
|
+
display += `⨠Net to creator: $${amount}.00\n`;
|
|
166
|
+
|
|
167
|
+
if (message) {
|
|
168
|
+
display += `\nš¬ _"${message}"_\n`;
|
|
118
169
|
}
|
|
170
|
+
|
|
171
|
+
display += `\nš [View on Explorer](${result.explorer_url})`;
|
|
172
|
+
|
|
173
|
+
// Build response with actions
|
|
174
|
+
const response_obj = {
|
|
175
|
+
display,
|
|
176
|
+
tx_hash: result.tx_hash,
|
|
177
|
+
explorer_url: result.explorer_url
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Add follow-up actions
|
|
181
|
+
response_obj.actions = formatActions(actions.afterTip ? actions.afterTip(recipient) : actions.afterDm(recipient));
|
|
182
|
+
|
|
183
|
+
return response_obj;
|
|
184
|
+
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error('[tip] Error:', error);
|
|
187
|
+
return {
|
|
188
|
+
display: `ā **Tip failed**\n\nError: ${error.message}\n\nPlease try again.`
|
|
189
|
+
};
|
|
119
190
|
}
|
|
120
|
-
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = { definition, handler };
|
package/tools/token.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* vibe token ā Set
|
|
2
|
+
* vibe token ā Set auth token after GitHub OAuth
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
5
|
* vibe token <paste-token-here>
|
|
@@ -44,7 +44,7 @@ If you haven't authenticated yet:
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// Verify token with server
|
|
47
|
-
const verification = await store.
|
|
47
|
+
const verification = await store.verifyAuthToken(token.trim());
|
|
48
48
|
|
|
49
49
|
if (!verification.valid) {
|
|
50
50
|
return {
|
|
@@ -60,7 +60,7 @@ The token may be expired or invalid. Try authenticating again:
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// Save token
|
|
63
|
-
config.
|
|
63
|
+
config.saveAuthToken(token.trim());
|
|
64
64
|
|
|
65
65
|
// Update session identity with verified handle
|
|
66
66
|
const handle = verification.handle;
|
|
@@ -96,7 +96,7 @@ You're ready to vibe! Try:
|
|
|
96
96
|
⢠\`vibe dm @someone "hello"\` ā Send a message`,
|
|
97
97
|
handle,
|
|
98
98
|
github: verification.github,
|
|
99
|
-
authMethod: '
|
|
99
|
+
authMethod: 'github'
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
|
package/tools/update.js
CHANGED
|
@@ -49,7 +49,7 @@ async function handler(args) {
|
|
|
49
49
|
display += `Your install doesn't support automatic updates.\n\n`;
|
|
50
50
|
display += `To update, re-run the installer:\n`;
|
|
51
51
|
display += `\`\`\`\n`;
|
|
52
|
-
display += `curl -fsSL https://raw.githubusercontent.com/
|
|
52
|
+
display += `curl -fsSL https://raw.githubusercontent.com/VibeCodingInc/vibe-mcp/main/install.sh | bash\n`;
|
|
53
53
|
display += `\`\`\`\n\n`;
|
|
54
54
|
display += `This will migrate you to the git-based installer for future updates.\n\n`;
|
|
55
55
|
display += `---\n`;
|
package/tools/wallet.js
CHANGED
|
@@ -1,37 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* vibe_wallet -
|
|
2
|
+
* vibe_wallet - Unified wallet dashboard
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* The "smoothbrain" view - everything in one place:
|
|
5
|
+
* - USD earnings (fiat)
|
|
6
|
+
* - $VIBE tokens (engagement currency)
|
|
7
|
+
* - Vesting schedule
|
|
8
|
+
* - Quick actions
|
|
5
9
|
*
|
|
6
10
|
* Examples:
|
|
7
11
|
* - "vibe wallet"
|
|
8
12
|
* - "check my balance"
|
|
9
|
-
* - "show
|
|
13
|
+
* - "show my $VIBE"
|
|
10
14
|
*/
|
|
11
15
|
|
|
12
16
|
const fetch = require('node-fetch');
|
|
13
17
|
|
|
14
18
|
module.exports = {
|
|
15
19
|
name: 'vibe_wallet',
|
|
16
|
-
description: 'Check your wallet
|
|
20
|
+
description: 'Check your wallet: USD earnings, $VIBE tokens, vesting schedule, and available actions.',
|
|
17
21
|
|
|
18
22
|
inputSchema: {
|
|
19
23
|
type: 'object',
|
|
20
24
|
properties: {
|
|
25
|
+
view: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Which view: dashboard (default), vesting, history',
|
|
28
|
+
enum: ['dashboard', 'vesting', 'history'],
|
|
29
|
+
default: 'dashboard'
|
|
30
|
+
},
|
|
21
31
|
limit: {
|
|
22
32
|
type: 'number',
|
|
23
|
-
description: 'Number of
|
|
33
|
+
description: 'Number of transactions/items to show (for history view)',
|
|
24
34
|
default: 10
|
|
25
|
-
},
|
|
26
|
-
show_balance: {
|
|
27
|
-
type: 'boolean',
|
|
28
|
-
description: 'Show current balance (default true)',
|
|
29
|
-
default: true
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
37
|
},
|
|
33
38
|
|
|
34
|
-
async execute({
|
|
39
|
+
async execute({ view = 'dashboard', limit = 10 }, context) {
|
|
35
40
|
try {
|
|
36
41
|
const handle = context.handle;
|
|
37
42
|
|
|
@@ -44,77 +49,17 @@ module.exports = {
|
|
|
44
49
|
|
|
45
50
|
const apiUrl = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
|
|
46
51
|
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
{
|
|
51
|
-
headers: {
|
|
52
|
-
'Authorization': `Bearer ${context.token}`
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
const historyResult = await historyResponse.json();
|
|
58
|
-
|
|
59
|
-
if (!historyResponse.ok) {
|
|
60
|
-
return {
|
|
61
|
-
success: false,
|
|
62
|
-
error: 'Failed to fetch wallet data',
|
|
63
|
-
details: historyResult.error
|
|
64
|
-
};
|
|
52
|
+
// Handle different views
|
|
53
|
+
if (view === 'vesting') {
|
|
54
|
+
return await fetchVestingSchedule(apiUrl, handle, context.token);
|
|
65
55
|
}
|
|
66
56
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
let balance = 0;
|
|
71
|
-
let totalReceived = 0;
|
|
72
|
-
let totalSent = 0;
|
|
73
|
-
|
|
74
|
-
transactions.forEach(tx => {
|
|
75
|
-
if (tx.type?.includes('received') || tx.type === 'escrow_completed') {
|
|
76
|
-
totalReceived += tx.amount || 0;
|
|
77
|
-
} else if (tx.type?.includes('sent') || tx.type === 'escrow_created') {
|
|
78
|
-
totalSent += tx.amount || 0;
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
balance = totalReceived - totalSent;
|
|
83
|
-
|
|
84
|
-
// Format transactions
|
|
85
|
-
const formattedTxs = transactions.slice(0, limit).map(tx => {
|
|
86
|
-
const isReceived = tx.type?.includes('received') || tx.type === 'escrow_completed';
|
|
87
|
-
const icon = isReceived ? 'ā' : 'ā';
|
|
88
|
-
const peer = isReceived ? tx.from : tx.to;
|
|
89
|
-
const amountStr = isReceived
|
|
90
|
-
? `+$${tx.amount?.toFixed(2) || '0.00'}`
|
|
91
|
-
: `-$${tx.amount?.toFixed(2) || '0.00'}`;
|
|
92
|
-
|
|
93
|
-
return `${icon} ${amountStr.padEnd(10)} ${peer || 'unknown'} (${tx.status})`;
|
|
94
|
-
});
|
|
57
|
+
if (view === 'history') {
|
|
58
|
+
return await fetchHistory(apiUrl, handle, limit, context.token);
|
|
59
|
+
}
|
|
95
60
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
wallet: {
|
|
99
|
-
handle,
|
|
100
|
-
balance: `$${balance.toFixed(2)}`,
|
|
101
|
-
total_received: `$${totalReceived.toFixed(2)}`,
|
|
102
|
-
total_sent: `$${totalSent.toFixed(2)}`,
|
|
103
|
-
transaction_count: transactions.length
|
|
104
|
-
},
|
|
105
|
-
formatted: `
|
|
106
|
-
š° Wallet: ${handle}
|
|
107
|
-
|
|
108
|
-
Balance: $${balance.toFixed(2)}
|
|
109
|
-
Received: $${totalReceived.toFixed(2)}
|
|
110
|
-
Sent: $${totalSent.toFixed(2)}
|
|
111
|
-
|
|
112
|
-
Recent Transactions (${Math.min(limit, transactions.length)}):
|
|
113
|
-
${formattedTxs.length > 0 ? formattedTxs.join('\n') : ' No transactions yet'}
|
|
114
|
-
|
|
115
|
-
${historyResult.has_more ? `\n(${transactions.length - limit} more transactions...)` : ''}
|
|
116
|
-
`.trim()
|
|
117
|
-
};
|
|
61
|
+
// Default: dashboard view
|
|
62
|
+
return await fetchDashboard(apiUrl, handle, context.token);
|
|
118
63
|
|
|
119
64
|
} catch (error) {
|
|
120
65
|
return {
|
|
@@ -125,3 +70,200 @@ ${historyResult.has_more ? `\n(${transactions.length - limit} more transactions.
|
|
|
125
70
|
}
|
|
126
71
|
}
|
|
127
72
|
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Fetch unified dashboard
|
|
76
|
+
*/
|
|
77
|
+
async function fetchDashboard(apiUrl, handle, token) {
|
|
78
|
+
const response = await fetch(
|
|
79
|
+
`${apiUrl}/api/wallet/dashboard?handle=${handle}`,
|
|
80
|
+
{ headers: { 'Authorization': `Bearer ${token}` } }
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const result = await response.json();
|
|
84
|
+
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
error: 'Failed to fetch wallet dashboard',
|
|
89
|
+
details: result.error
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Build formatted output
|
|
94
|
+
const usd = result.usd;
|
|
95
|
+
const vibe = result.vibe;
|
|
96
|
+
const summary = result.summary;
|
|
97
|
+
const actions = result.actions || [];
|
|
98
|
+
|
|
99
|
+
// USD section
|
|
100
|
+
const usdSection = usd.available.cents > 0
|
|
101
|
+
? `šµ USD Earnings
|
|
102
|
+
Available: ${usd.available.display}
|
|
103
|
+
${usd.held.cents > 0 ? `Held: ${usd.held.display}\n ` : ''}Total earned: ${usd.total_earned.display}
|
|
104
|
+
${usd.can_withdraw ? 'ā
Ready to withdraw' : usd.payout_enabled ? 'ā³ No balance to withdraw' : 'ā ļø Connect bank to withdraw'}`
|
|
105
|
+
: `šµ USD Earnings
|
|
106
|
+
No earnings yet. Complete gigs to earn!`;
|
|
107
|
+
|
|
108
|
+
// $VIBE section
|
|
109
|
+
const vibeSection = `
|
|
110
|
+
š $VIBE Tokens
|
|
111
|
+
Spendable: ${vibe.liquid} $VIBE
|
|
112
|
+
Vesting: ${vibe.vesting} $VIBE
|
|
113
|
+
āāāāāāāāāāāāāāāāā
|
|
114
|
+
Total: ${vibe.total} $VIBE
|
|
115
|
+
|
|
116
|
+
${vibe.upcoming.this_week > 0 ? `š
+${vibe.upcoming.this_week} unlocks this week` : ''}
|
|
117
|
+
${vibe.upcoming.this_month > 0 && vibe.upcoming.this_month !== vibe.upcoming.this_week ? `š
+${vibe.upcoming.this_month} unlocks this month` : ''}
|
|
118
|
+
${vibe.next_unlock ? `ā° Next: ${vibe.next_unlock.amount} $VIBE in ${vibe.next_unlock.days} days` : ''}
|
|
119
|
+
|
|
120
|
+
Daily: ${vibe.daily.earned}/${vibe.daily.cap} earned today
|
|
121
|
+
${'ā'.repeat(Math.floor(vibe.daily.percent / 10))}${'ā'.repeat(10 - Math.floor(vibe.daily.percent / 10))} ${vibe.daily.percent}%`;
|
|
122
|
+
|
|
123
|
+
// Actions section
|
|
124
|
+
const actionsSection = actions.length > 0
|
|
125
|
+
? `\nšÆ Actions\n${actions.slice(0, 3).map(a => ` ⢠${a.label}: ${a.description}`).join('\n')}`
|
|
126
|
+
: '';
|
|
127
|
+
|
|
128
|
+
const formatted = `
|
|
129
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā®
|
|
130
|
+
ā š° WALLET: @${handle.padEnd(17)}ā
|
|
131
|
+
ā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāÆ
|
|
132
|
+
|
|
133
|
+
${usdSection}
|
|
134
|
+
|
|
135
|
+
${vibeSection.trim()}
|
|
136
|
+
${actionsSection}
|
|
137
|
+
|
|
138
|
+
Tip: Use "vibe wallet --view vesting" for full schedule
|
|
139
|
+
`.trim();
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
success: true,
|
|
143
|
+
handle,
|
|
144
|
+
usd: result.usd,
|
|
145
|
+
vibe: result.vibe,
|
|
146
|
+
actions: result.actions,
|
|
147
|
+
formatted
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Fetch vesting schedule
|
|
153
|
+
*/
|
|
154
|
+
async function fetchVestingSchedule(apiUrl, handle, token) {
|
|
155
|
+
const response = await fetch(
|
|
156
|
+
`${apiUrl}/api/vibe/vesting?handle=${handle}`,
|
|
157
|
+
{ headers: { 'Authorization': `Bearer ${token}` } }
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const result = await response.json();
|
|
161
|
+
|
|
162
|
+
if (!response.ok) {
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
error: 'Failed to fetch vesting schedule',
|
|
166
|
+
details: result.error
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Format vesting schedule
|
|
171
|
+
const summary = result.summary;
|
|
172
|
+
const unlocks = result.unlock_schedule || [];
|
|
173
|
+
const breakdown = result.activity_breakdown || [];
|
|
174
|
+
|
|
175
|
+
const scheduleLines = unlocks.slice(0, 8).map(week => {
|
|
176
|
+
const bar = 'ā'.repeat(Math.min(10, Math.ceil(week.total_amount / 10)));
|
|
177
|
+
return ` ${week.week_label.padEnd(12)} +${week.total_amount} $VIBE ${bar}`;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const breakdownLines = breakdown.slice(0, 5).map(act => {
|
|
181
|
+
return ` ${act.reason_display.padEnd(24)} ${act.amount} $VIBE`;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const formatted = `
|
|
185
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā®
|
|
186
|
+
ā š
VESTING SCHEDULE: @${handle.padEnd(10)}ā
|
|
187
|
+
ā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāÆ
|
|
188
|
+
|
|
189
|
+
Summary
|
|
190
|
+
Liquid (spendable): ${summary.liquid} $VIBE
|
|
191
|
+
Vesting (locked): ${summary.vesting} $VIBE
|
|
192
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
193
|
+
Total: ${summary.total} $VIBE
|
|
194
|
+
|
|
195
|
+
${result.next_unlock ? `Next Unlock
|
|
196
|
+
${result.next_unlock.amount} $VIBE in ${result.next_unlock.days_remaining} days
|
|
197
|
+
(earned from: ${result.next_unlock.reason})
|
|
198
|
+
` : ''}
|
|
199
|
+
Upcoming Unlocks by Week
|
|
200
|
+
${scheduleLines.length > 0 ? scheduleLines.join('\n') : ' No tokens vesting'}
|
|
201
|
+
|
|
202
|
+
Earned By Activity
|
|
203
|
+
${breakdownLines.length > 0 ? breakdownLines.join('\n') : ' No activity yet'}
|
|
204
|
+
|
|
205
|
+
ā¹ļø Tokens vest over ${result.vesting_period_days} days to reward long-term builders.
|
|
206
|
+
`.trim();
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
success: true,
|
|
210
|
+
handle,
|
|
211
|
+
summary: result.summary,
|
|
212
|
+
schedule: result.unlock_schedule,
|
|
213
|
+
breakdown: result.activity_breakdown,
|
|
214
|
+
formatted
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Fetch transaction history
|
|
220
|
+
*/
|
|
221
|
+
async function fetchHistory(apiUrl, handle, limit, token) {
|
|
222
|
+
const response = await fetch(
|
|
223
|
+
`${apiUrl}/api/vibe/history?handle=${handle}&limit=${limit}`,
|
|
224
|
+
{ headers: { 'Authorization': `Bearer ${token}` } }
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const result = await response.json();
|
|
228
|
+
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
return {
|
|
231
|
+
success: false,
|
|
232
|
+
error: 'Failed to fetch history',
|
|
233
|
+
details: result.error
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const transactions = result.transactions || [];
|
|
238
|
+
|
|
239
|
+
const txLines = transactions.map(tx => {
|
|
240
|
+
const icon = tx.amount > 0 ? 'ā' : 'ā';
|
|
241
|
+
const sign = tx.amount > 0 ? '+' : '';
|
|
242
|
+
const status = tx.status === 'vesting' ? 'š' : 'ā';
|
|
243
|
+
const date = new Date(tx.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
244
|
+
return ` ${icon} ${sign}${tx.amount} $VIBE ${tx.reason.padEnd(20)} ${status} ${date}`;
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const formatted = `
|
|
248
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā®
|
|
249
|
+
ā š HISTORY: @${handle.padEnd(17)}ā
|
|
250
|
+
ā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāÆ
|
|
251
|
+
|
|
252
|
+
Balance: ${result.balance.liquid} liquid / ${result.balance.vesting} vesting
|
|
253
|
+
|
|
254
|
+
Recent Transactions
|
|
255
|
+
${txLines.length > 0 ? txLines.join('\n') : ' No transactions yet'}
|
|
256
|
+
|
|
257
|
+
${result.pagination.has_more ? `\n(showing ${limit} of more...)` : ''}
|
|
258
|
+
|
|
259
|
+
Legend: ā earned ā spent š vesting ā liquid
|
|
260
|
+
`.trim();
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
success: true,
|
|
264
|
+
handle,
|
|
265
|
+
balance: result.balance,
|
|
266
|
+
transactions: result.transactions,
|
|
267
|
+
formatted
|
|
268
|
+
};
|
|
269
|
+
}
|