slashvibe-mcp 0.3.20 → 0.3.21
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 +47 -252
- 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 +116 -159
- 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 +21 -16
- package/prompts.js +1 -1
- package/protocol/index.js +73 -0
- package/setup.js +480 -0
- package/smart-inbox.js +276 -0
- package/store/api.js +536 -215
- 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 +325 -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/health.js +87 -0
- package/tools/help.js +3 -3
- package/tools/idea.js +9 -2
- package/tools/inbox.js +289 -105
- package/tools/init.js +131 -34
- package/tools/invite.js +15 -4
- package/tools/leaderboard.js +117 -0
- package/tools/lib/git-apply.js +206 -0
- package/tools/lib/git-bundle.js +407 -0
- 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 +467 -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/streak.js +147 -0
- 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/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
|
+
}
|
package/tools/watch.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe watch — Watch live broadcasts
|
|
3
|
+
*
|
|
4
|
+
* Discover and watch live coding sessions from the /vibe community.
|
|
5
|
+
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* - watch → List active broadcasts
|
|
8
|
+
* - watch @handle → Watch specific person's broadcast
|
|
9
|
+
* - watch room_xxx → Watch by room ID
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const config = require('../config');
|
|
13
|
+
|
|
14
|
+
const definition = {
|
|
15
|
+
name: 'vibe_watch',
|
|
16
|
+
description: 'See who\'s live and watch their coding sessions in real-time.',
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
target: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'Handle (@user) or room ID to watch. Omit to list all live broadcasts.'
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
async function handler(args) {
|
|
29
|
+
const apiUrl = config.getApiUrl();
|
|
30
|
+
const target = args.target;
|
|
31
|
+
|
|
32
|
+
// LIST all broadcasts
|
|
33
|
+
if (!target) {
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch(`${apiUrl}/api/live`, {
|
|
36
|
+
headers: { 'Accept': 'application/json' }
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const data = await response.json();
|
|
40
|
+
|
|
41
|
+
if (!data.success) {
|
|
42
|
+
return { display: `⚠️ Failed to fetch live broadcasts: ${data.error}` };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!data.broadcasts || data.broadcasts.length === 0) {
|
|
46
|
+
let display = `📺 **No Live Broadcasts**\n\n`;
|
|
47
|
+
display += `_No one is streaming right now._\n\n`;
|
|
48
|
+
display += `**Start your own:**\n`;
|
|
49
|
+
display += `\`vibe broadcast start "What you're building"\`\n\n`;
|
|
50
|
+
display += `**Browse saved sessions:**\n`;
|
|
51
|
+
display += `\`vibe session list\``;
|
|
52
|
+
return { display };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const formatDuration = (secs) => {
|
|
56
|
+
if (secs < 60) return 'just started';
|
|
57
|
+
if (secs < 3600) return `${Math.floor(secs / 60)}m`;
|
|
58
|
+
return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
let display = `📺 **${data.broadcasts.length} Live Now**\n\n`;
|
|
62
|
+
|
|
63
|
+
for (const b of data.broadcasts) {
|
|
64
|
+
const viewers = b.viewers || 0;
|
|
65
|
+
const duration = formatDuration(b.duration || 0);
|
|
66
|
+
|
|
67
|
+
display += `🔴 **@${b.handle}**`;
|
|
68
|
+
if (viewers > 0) {
|
|
69
|
+
display += ` — ${viewers} watching`;
|
|
70
|
+
}
|
|
71
|
+
display += ` — ${duration}\n`;
|
|
72
|
+
display += ` → \`vibe watch ${b.roomId}\`\n\n`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
display += `_Watch a stream or start your own with \`vibe broadcast start\`_`;
|
|
76
|
+
|
|
77
|
+
return { display };
|
|
78
|
+
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return { display: `## Watch Error\n\n${error.message}` };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// WATCH specific broadcast
|
|
85
|
+
const isRoomId = target.startsWith('room_');
|
|
86
|
+
const isHandle = target.startsWith('@') || !target.includes('_');
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
let roomId = target;
|
|
90
|
+
let broadcasterHandle = null;
|
|
91
|
+
|
|
92
|
+
// If target is a handle, find their room
|
|
93
|
+
if (isHandle) {
|
|
94
|
+
broadcasterHandle = target.replace('@', '').toLowerCase();
|
|
95
|
+
|
|
96
|
+
// Fetch live broadcasts to find this person's room
|
|
97
|
+
const liveResponse = await fetch(`${apiUrl}/api/live`, {
|
|
98
|
+
headers: { 'Accept': 'application/json' }
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const liveData = await liveResponse.json();
|
|
102
|
+
|
|
103
|
+
if (!liveData.success || !liveData.broadcasts) {
|
|
104
|
+
return { display: `⚠️ Failed to fetch broadcasts` };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const broadcast = liveData.broadcasts.find(
|
|
108
|
+
b => b.handle.toLowerCase() === broadcasterHandle
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (!broadcast) {
|
|
112
|
+
return {
|
|
113
|
+
display: `📺 @${broadcasterHandle} is not live right now.\n\n_Check who's streaming with \`vibe watch\`_`
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
roomId = broadcast.roomId;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Get broadcast info
|
|
121
|
+
const response = await fetch(`${apiUrl}/api/watch?room=${roomId}`);
|
|
122
|
+
const data = await response.json();
|
|
123
|
+
|
|
124
|
+
if (!data.success) {
|
|
125
|
+
return {
|
|
126
|
+
display: `📺 Broadcast not found or ended.\n\n_Check live streams with \`vibe watch\`_`
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const broadcast = data.broadcast;
|
|
131
|
+
const duration = broadcast.startedAt
|
|
132
|
+
? Math.round((Date.now() - new Date(broadcast.startedAt).getTime()) / 1000)
|
|
133
|
+
: 0;
|
|
134
|
+
|
|
135
|
+
const formatDuration = (secs) => {
|
|
136
|
+
if (secs < 60) return 'just started';
|
|
137
|
+
if (secs < 3600) return `${Math.floor(secs / 60)}m`;
|
|
138
|
+
return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
let display = `🔴 **LIVE: @${broadcast.handle}**\n\n`;
|
|
142
|
+
display += `**Duration:** ${formatDuration(duration)}\n`;
|
|
143
|
+
display += `**Viewers:** ${broadcast.viewerCount || 0}\n\n`;
|
|
144
|
+
display += `**Watch in browser:**\n`;
|
|
145
|
+
display += `https://slashvibe.dev/watch/${roomId}\n\n`;
|
|
146
|
+
display += `_Open the URL above to watch the live terminal stream._\n\n`;
|
|
147
|
+
display += `---\n`;
|
|
148
|
+
display += `_Coming soon: inline terminal replay in Claude Code_`;
|
|
149
|
+
|
|
150
|
+
return { display };
|
|
151
|
+
|
|
152
|
+
} catch (error) {
|
|
153
|
+
return { display: `## Watch Error\n\n${error.message}` };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = { definition, handler };
|
package/tools/who.js
CHANGED
|
@@ -150,6 +150,19 @@ async function handler(args) {
|
|
|
150
150
|
// Apply smart detection — infer states from context signals
|
|
151
151
|
const users = enhanceUsersWithInference(rawUsers);
|
|
152
152
|
const myHandle = config.getHandle();
|
|
153
|
+
const apiUrl = config.getApiUrl();
|
|
154
|
+
|
|
155
|
+
// Fetch active help requests (non-blocking)
|
|
156
|
+
let helpRequests = [];
|
|
157
|
+
try {
|
|
158
|
+
const helpResponse = await fetch(`${apiUrl}/api/help?limit=5`);
|
|
159
|
+
const helpData = await helpResponse.json();
|
|
160
|
+
if (helpData.success && helpData.requests) {
|
|
161
|
+
helpRequests = helpData.requests;
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {
|
|
164
|
+
// Silent fail - help requests are nice-to-have
|
|
165
|
+
}
|
|
153
166
|
|
|
154
167
|
// Check for notifications (presence + messages)
|
|
155
168
|
notify.checkAll(store);
|
|
@@ -177,6 +190,17 @@ _Check back in a bit — builders come and go._`
|
|
|
177
190
|
|
|
178
191
|
let display = `## Who's Around\n\n`;
|
|
179
192
|
|
|
193
|
+
// Help requests section (if any)
|
|
194
|
+
if (helpRequests.length > 0) {
|
|
195
|
+
display += `**🆘 Needs Help:**\n`;
|
|
196
|
+
for (const req of helpRequests.slice(0, 3)) {
|
|
197
|
+
const urgencyIcon = req.urgency === 'high' ? '🔴' : req.urgency === 'low' ? '🟢' : '🟡';
|
|
198
|
+
const shortProblem = req.problem.length > 50 ? req.problem.slice(0, 47) + '...' : req.problem;
|
|
199
|
+
display += `${urgencyIcon} **@${req.handle}**: ${shortProblem}\n`;
|
|
200
|
+
}
|
|
201
|
+
display += `→ \`vibe stuck\` to help or ask\n\n---\n\n`;
|
|
202
|
+
}
|
|
203
|
+
|
|
180
204
|
// Activity section for active users
|
|
181
205
|
if (active.length > 0) {
|
|
182
206
|
active.forEach(u => {
|
|
@@ -190,7 +214,12 @@ _Check back in a bit — builders come and go._`
|
|
|
190
214
|
const activity = formatActivity(u);
|
|
191
215
|
const timeAgo = formatTimeAgo(u.lastSeen);
|
|
192
216
|
|
|
193
|
-
|
|
217
|
+
// Show availability topics if set
|
|
218
|
+
const availableFor = u.availableFor && Array.isArray(u.availableFor) && u.availableFor.length > 0
|
|
219
|
+
? ` — 🤝 _available for: ${u.availableFor.join(', ')}_`
|
|
220
|
+
: '';
|
|
221
|
+
|
|
222
|
+
display += `${heat.icon} **@${u.handle}**${agentBadge}${tag}${heatLabel}${availableFor}\n`;
|
|
194
223
|
if (operatorTag) {
|
|
195
224
|
display += ` ${operatorTag}\n`;
|
|
196
225
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe_withdraw - Withdraw earnings from FeeRouter
|
|
3
|
+
*
|
|
4
|
+
* Generates transaction data to withdraw accumulated on-chain earnings.
|
|
5
|
+
* User must sign and submit the transaction with their wallet.
|
|
6
|
+
*
|
|
7
|
+
* Examples:
|
|
8
|
+
* - "vibe withdraw"
|
|
9
|
+
* - "withdraw my earnings"
|
|
10
|
+
* - "claim my on-chain balance"
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fetch = require('node-fetch');
|
|
14
|
+
const config = require('../config');
|
|
15
|
+
|
|
16
|
+
const definition = {
|
|
17
|
+
name: 'vibe_withdraw',
|
|
18
|
+
description: 'Withdraw accumulated earnings from the FeeRouter. Generates transaction data for you to sign.',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
to_address: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'Optional destination address (defaults to your registered payout wallet)'
|
|
25
|
+
},
|
|
26
|
+
check_only: {
|
|
27
|
+
type: 'boolean',
|
|
28
|
+
description: 'Only check balance without generating withdrawal transaction'
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
async function handler(args) {
|
|
35
|
+
const { to_address, check_only = false } = args;
|
|
36
|
+
|
|
37
|
+
if (!config.isInitialized()) {
|
|
38
|
+
return {
|
|
39
|
+
display: 'Run `vibe init` first to set your identity.'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const handle = config.getHandle();
|
|
44
|
+
const token = config.getToken();
|
|
45
|
+
const apiUrl = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// Check-only mode
|
|
49
|
+
if (check_only) {
|
|
50
|
+
const response = await fetch(
|
|
51
|
+
`${apiUrl}/api/earnings/withdraw?handle=${encodeURIComponent(handle)}`,
|
|
52
|
+
{
|
|
53
|
+
headers: token ? {
|
|
54
|
+
'Authorization': `Bearer ${token}`
|
|
55
|
+
} : {}
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const result = await response.json();
|
|
60
|
+
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
return {
|
|
63
|
+
display: `❌ ${result.error || 'Failed to check balance'}`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const statusEmoji = result.can_withdraw ? '🟢' : '🟡';
|
|
68
|
+
const action = result.can_withdraw
|
|
69
|
+
? 'Run "vibe withdraw" to generate withdrawal transaction.'
|
|
70
|
+
: result.registered
|
|
71
|
+
? 'No balance to withdraw yet.'
|
|
72
|
+
: 'Register on-chain first: vibe register_onchain';
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
display: `
|
|
76
|
+
💰 Withdrawal Status: @${handle}
|
|
77
|
+
|
|
78
|
+
Balance: ${result.balance_eth} ETH
|
|
79
|
+
Registered: ${result.registered ? 'Yes' : 'No'}
|
|
80
|
+
Can Withdraw: ${statusEmoji} ${result.can_withdraw ? 'Yes' : 'No'}
|
|
81
|
+
|
|
82
|
+
${action}
|
|
83
|
+
`.trim(),
|
|
84
|
+
data: result
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Generate withdrawal transaction
|
|
89
|
+
const response = await fetch(
|
|
90
|
+
`${apiUrl}/api/earnings/withdraw`,
|
|
91
|
+
{
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers: {
|
|
94
|
+
'Content-Type': 'application/json',
|
|
95
|
+
'Authorization': `Bearer ${token}`
|
|
96
|
+
},
|
|
97
|
+
body: JSON.stringify({
|
|
98
|
+
to_address: to_address || undefined
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const result = await response.json();
|
|
104
|
+
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
let display = `❌ ${result.error || 'Failed to generate withdrawal'}`;
|
|
107
|
+
if (result.action) {
|
|
108
|
+
display += `\n\nNext step: ${result.action}`;
|
|
109
|
+
}
|
|
110
|
+
return { display };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const destDisplay = result.destination
|
|
114
|
+
? `${result.destination.slice(0, 10)}...${result.destination.slice(-8)}`
|
|
115
|
+
: 'your wallet';
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
display: `
|
|
119
|
+
💸 Withdrawal Ready: @${handle}
|
|
120
|
+
|
|
121
|
+
Amount: ${result.balance_display}
|
|
122
|
+
Destination: ${destDisplay}
|
|
123
|
+
Network: ${result.network}
|
|
124
|
+
|
|
125
|
+
Transaction to sign:
|
|
126
|
+
To: ${result.transaction.to}
|
|
127
|
+
Data: ${result.transaction.data?.slice(0, 40)}...
|
|
128
|
+
Gas: ${result.transaction.gas}
|
|
129
|
+
|
|
130
|
+
Instructions:
|
|
131
|
+
${result.instructions?.map((i, idx) => ` ${idx + 1}. ${i}`).join('\n')}
|
|
132
|
+
|
|
133
|
+
Explorer: ${result.explorer}
|
|
134
|
+
`.trim(),
|
|
135
|
+
data: result
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
} catch (error) {
|
|
139
|
+
return {
|
|
140
|
+
display: `❌ Failed to process withdrawal: ${error.message}`
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = { definition, handler };
|