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.
Files changed (161) hide show
  1. package/README.md +41 -58
  2. package/analytics.js +107 -0
  3. package/auth-store.js +148 -0
  4. package/auto-update.js +130 -0
  5. package/bridges/bridge-monitor.js +388 -0
  6. package/bridges/discord-bot.js +431 -0
  7. package/bridges/farcaster.js +299 -0
  8. package/bridges/telegram.js +261 -0
  9. package/bridges/webhook-health.js +420 -0
  10. package/bridges/webhook-server.js +437 -0
  11. package/bridges/whatsapp.js +441 -0
  12. package/bridges/x-webhook.js +423 -0
  13. package/config.js +27 -15
  14. package/games/arcade.js +406 -0
  15. package/games/chess.js +451 -0
  16. package/games/colorguess.js +343 -0
  17. package/games/crossword-words.js +171 -0
  18. package/games/crossword.js +461 -0
  19. package/games/drawing.js +347 -0
  20. package/games/gameroulette.js +300 -0
  21. package/games/gamerouter.js +336 -0
  22. package/games/gamestatus.js +337 -0
  23. package/games/guessnumber.js +209 -0
  24. package/games/hangman.js +279 -0
  25. package/games/memory.js +338 -0
  26. package/games/multiplayer-tictactoe.js +389 -0
  27. package/games/pixelart.js +399 -0
  28. package/games/quickduel.js +354 -0
  29. package/games/riddle.js +371 -0
  30. package/games/rockpaperscissors.js +291 -0
  31. package/games/snake.js +406 -0
  32. package/games/storybuilder.js +343 -0
  33. package/games/tictactoe.js +345 -0
  34. package/games/twentyquestions.js +286 -0
  35. package/games/twotruths.js +207 -0
  36. package/games/werewolf.js +508 -0
  37. package/games/wordassociation.js +247 -0
  38. package/games/wordchain.js +135 -0
  39. package/index.js +77 -53
  40. package/intelligence/index.js +9 -2
  41. package/intelligence/interests.js +369 -0
  42. package/notification-emitter.js +77 -0
  43. package/notify.js +5 -1
  44. package/package.json +18 -6
  45. package/prompts.js +1 -1
  46. package/protocol/index.js +73 -0
  47. package/setup.js +402 -0
  48. package/store/api.js +436 -211
  49. package/store/profiles.js +160 -12
  50. package/tools/_actions.js +362 -21
  51. package/tools/_discovery.js +119 -26
  52. package/tools/_shared/index.js +64 -0
  53. package/tools/_shared.js +234 -0
  54. package/tools/_work-context.js +338 -0
  55. package/tools/_work-context.manual-test.js +199 -0
  56. package/tools/_work-context.test.js +260 -0
  57. package/tools/activity.js +220 -0
  58. package/tools/analytics.js +191 -0
  59. package/tools/approve.js +197 -0
  60. package/tools/artifact-create.js +14 -3
  61. package/tools/artifacts-price.js +107 -0
  62. package/tools/available.js +120 -0
  63. package/tools/broadcast.js +286 -0
  64. package/tools/chat.js +202 -0
  65. package/tools/collaborative-drawing.js +1 -1
  66. package/tools/connection-status.js +178 -0
  67. package/tools/discover.js +350 -34
  68. package/tools/dm.js +80 -8
  69. package/tools/earnings.js +126 -0
  70. package/tools/feed.js +35 -4
  71. package/tools/follow.js +224 -0
  72. package/tools/friends.js +207 -0
  73. package/tools/gig-browse.js +206 -0
  74. package/tools/gig-complete.js +144 -0
  75. package/tools/help.js +3 -3
  76. package/tools/idea.js +9 -2
  77. package/tools/inbox.js +289 -105
  78. package/tools/init.js +106 -27
  79. package/tools/invite.js +15 -4
  80. package/tools/migrate.js +3 -3
  81. package/tools/multiplayer-game.js +1 -1
  82. package/tools/onboarding.js +7 -7
  83. package/tools/open.js +143 -12
  84. package/tools/party-game.js +1 -1
  85. package/tools/plan.js +225 -0
  86. package/tools/proof-of-work.js +144 -0
  87. package/tools/reply.js +166 -0
  88. package/tools/report.js +1 -1
  89. package/tools/request.js +17 -3
  90. package/tools/schedule.js +367 -0
  91. package/tools/search-messages.js +123 -0
  92. package/tools/session.js +420 -0
  93. package/tools/session_price.js +128 -0
  94. package/tools/settings.js +90 -2
  95. package/tools/ship.js +30 -7
  96. package/tools/smart-check.js +201 -0
  97. package/tools/start.js +147 -12
  98. package/tools/status.js +53 -6
  99. package/tools/stuck.js +297 -0
  100. package/tools/subscribe.js +148 -0
  101. package/tools/subscriptions.js +134 -0
  102. package/tools/suggest-tags.js +6 -8
  103. package/tools/tag-suggestions.js +1 -1
  104. package/tools/tip.js +150 -77
  105. package/tools/token.js +4 -4
  106. package/tools/update.js +1 -1
  107. package/tools/wallet.js +221 -79
  108. package/tools/watch.js +157 -0
  109. package/tools/who.js +30 -1
  110. package/tools/withdraw.js +145 -0
  111. package/tools/work-summary.js +96 -0
  112. package/version.json +10 -8
  113. package/LICENSE +0 -21
  114. package/store/sqlite.js +0 -347
  115. /package/tools/{auto-suggest-connections.js → _deprecated/auto-suggest-connections.js} +0 -0
  116. /package/tools/{away.js → _deprecated/away.js} +0 -0
  117. /package/tools/{back.js → _deprecated/back.js} +0 -0
  118. /package/tools/{bootstrap-skills.js → _deprecated/bootstrap-skills.js} +0 -0
  119. /package/tools/{bridge-dashboard.js → _deprecated/bridge-dashboard.js} +0 -0
  120. /package/tools/{bridge-health.js → _deprecated/bridge-health.js} +0 -0
  121. /package/tools/{bridge-live.js → _deprecated/bridge-live.js} +0 -0
  122. /package/tools/{bridges.js → _deprecated/bridges.js} +0 -0
  123. /package/tools/{colorguess.js → _deprecated/colorguess.js} +0 -0
  124. /package/tools/{discover-insights.js → _deprecated/discover-insights.js} +0 -0
  125. /package/tools/{discover-momentum.js → _deprecated/discover-momentum.js} +0 -0
  126. /package/tools/{discovery-analytics.js → _deprecated/discovery-analytics.js} +0 -0
  127. /package/tools/{discovery-auto-suggest.js → _deprecated/discovery-auto-suggest.js} +0 -0
  128. /package/tools/{discovery-bootstrap.js → _deprecated/discovery-bootstrap.js} +0 -0
  129. /package/tools/{discovery-daily.js → _deprecated/discovery-daily.js} +0 -0
  130. /package/tools/{discovery-dashboard.js → _deprecated/discovery-dashboard.js} +0 -0
  131. /package/tools/{discovery-digest.js → _deprecated/discovery-digest.js} +0 -0
  132. /package/tools/{discovery-hub.js → _deprecated/discovery-hub.js} +0 -0
  133. /package/tools/{discovery-insights.js → _deprecated/discovery-insights.js} +0 -0
  134. /package/tools/{discovery-momentum.js → _deprecated/discovery-momentum.js} +0 -0
  135. /package/tools/{discovery-monitor.js → _deprecated/discovery-monitor.js} +0 -0
  136. /package/tools/{discovery-proactive.js → _deprecated/discovery-proactive.js} +0 -0
  137. /package/tools/{draw.js → _deprecated/draw.js} +0 -0
  138. /package/tools/{farcaster.js → _deprecated/farcaster.js} +0 -0
  139. /package/tools/{forget.js → _deprecated/forget.js} +0 -0
  140. /package/tools/{games-catalog.js → _deprecated/games-catalog.js} +0 -0
  141. /package/tools/{games.js → _deprecated/games.js} +0 -0
  142. /package/tools/{guessnumber.js → _deprecated/guessnumber.js} +0 -0
  143. /package/tools/{hangman.js → _deprecated/hangman.js} +0 -0
  144. /package/tools/{multiplayer-tictactoe.js → _deprecated/multiplayer-tictactoe.js} +0 -0
  145. /package/tools/{mute.js → _deprecated/mute.js} +0 -0
  146. /package/tools/{recall.js → _deprecated/recall.js} +0 -0
  147. /package/tools/{remember.js → _deprecated/remember.js} +0 -0
  148. /package/tools/{riddle.js → _deprecated/riddle.js} +0 -0
  149. /package/tools/{run-bootstrap.js → _deprecated/run-bootstrap.js} +0 -0
  150. /package/tools/{skills-analytics.js → _deprecated/skills-analytics.js} +0 -0
  151. /package/tools/{skills-bootstrap.js → _deprecated/skills-bootstrap.js} +0 -0
  152. /package/tools/{skills-dashboard.js → _deprecated/skills-dashboard.js} +0 -0
  153. /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
  154. /package/tools/{skills.js → _deprecated/skills.js} +0 -0
  155. /package/tools/{smart-intro.js → _deprecated/smart-intro.js} +0 -0
  156. /package/tools/{storybuilder.js → _deprecated/storybuilder.js} +0 -0
  157. /package/tools/{telegram-bot.js → _deprecated/telegram-bot.js} +0 -0
  158. /package/tools/{telegram-setup.js → _deprecated/telegram-setup.js} +0 -0
  159. /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
  160. /package/tools/{twentyquestions.js → _deprecated/twentyquestions.js} +0 -0
  161. /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
package/tools/tip.js CHANGED
@@ -1,20 +1,32 @@
1
1
  /**
2
- * vibe_tip - Send a tip to another user
2
+ * vibe_tip - Send an instant on-chain USDC tip
3
3
  *
4
- * Makes the entire payment layer accessible from Claude Code conversations
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 $10 thanks for the intro"
9
- * - "vibe tip @charlie 2.50"
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 fetch = require('node-fetch');
16
+ const config = require('../config');
17
+ const { requireInit, normalizeHandle, displayHandle } = require('./_shared');
18
+ const { actions, formatActions } = require('./_actions');
13
19
 
14
- module.exports = {
15
- name: 'vibe_tip',
16
- description: 'Send a tip to another user. Tips are instant payments via blockchain (2.5% fee). Example: vibe_tip @alice 5 "thanks for the help!"',
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 (e.g., 5.00). Min $0.01, Max $100'
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', 'amount']
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
- async execute({ to, amount, message }, context) {
38
- try {
39
- const from = context.handle;
106
+ // Call instant tip API
107
+ const apiUrl = config.getApiUrl();
40
108
 
41
- if (!from) {
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
- success: false,
44
- error: 'Not authenticated. Use vibe init first.'
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
- // Validate amount
49
- if (amount <= 0 || amount > 100) {
141
+ if (result.error === 'recipient_no_wallet') {
50
142
  return {
51
- success: false,
52
- error: 'Amount must be between $0.01 and $100'
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
- // Call payment API
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
- success: false,
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
- success: true,
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
- } catch (error) {
113
- return {
114
- success: false,
115
- error: 'Tip failed',
116
- details: error.message
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 Privy auth token after browser OAuth
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.verifyPrivyToken(token.trim());
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.savePrivyToken(token.trim());
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: 'privy'
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/brightseth/vibe/main/install.sh | bash\n`;
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 - Check wallet balance and transaction history
2
+ * vibe_wallet - Unified wallet dashboard
3
3
  *
4
- * View your economic state: balance, recent transactions, earnings
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 recent transactions"
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 balance and recent transactions. Shows tips sent/received, escrows, and current balance.',
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 recent transactions to show (default 10, max 50)',
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({ limit = 10, show_balance = true }, context) {
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
- // Fetch transaction history
48
- const historyResponse = await fetch(
49
- `${apiUrl}/api/payments/history?handle=${handle}&limit=${Math.min(limit, 50)}`,
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
- const transactions = historyResult.transactions || [];
68
-
69
- // Calculate balance from transactions (simplified)
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
- return {
97
- success: true,
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
+ }