slashvibe-mcp 0.2.2 → 0.2.3

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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -40
  3. package/config.js +171 -3
  4. package/index.js +136 -16
  5. package/intelligence/index.js +38 -0
  6. package/intelligence/infer.js +316 -0
  7. package/intelligence/patterns.js +651 -0
  8. package/intelligence/proactive.js +358 -0
  9. package/intelligence/serendipity.js +306 -0
  10. package/notify.js +141 -18
  11. package/package.json +8 -4
  12. package/presence.js +5 -1
  13. package/protocol/index.js +88 -1
  14. package/protocol/telegram-commands.js +199 -0
  15. package/store/api.js +360 -25
  16. package/store/index.js +7 -7
  17. package/store/local.js +67 -11
  18. package/store/profiles.js +287 -0
  19. package/store/reservations.js +321 -0
  20. package/store/skills.js +378 -0
  21. package/tools/_actions.js +270 -14
  22. package/tools/_connection-queue.js +257 -0
  23. package/tools/_discovery-enhanced.js +290 -0
  24. package/tools/_discovery.js +346 -0
  25. package/tools/_proactive-discovery.js +301 -0
  26. package/tools/admin-inbox.js +218 -0
  27. package/tools/agent-treasury.js +288 -0
  28. package/tools/agents.js +122 -0
  29. package/tools/arcade.js +173 -0
  30. package/tools/artifact-create.js +236 -0
  31. package/tools/artifact-view.js +174 -0
  32. package/tools/ask-expert.js +160 -0
  33. package/tools/auto-suggest-connections.js +304 -0
  34. package/tools/away.js +68 -0
  35. package/tools/back.js +51 -0
  36. package/tools/become-expert.js +150 -0
  37. package/tools/bootstrap-skills.js +231 -0
  38. package/tools/bridge-dashboard.js +342 -0
  39. package/tools/bridge-health.js +400 -0
  40. package/tools/bridge-live.js +384 -0
  41. package/tools/bridges.js +383 -0
  42. package/tools/bye.js +4 -0
  43. package/tools/collaborative-drawing.js +286 -0
  44. package/tools/colorguess.js +281 -0
  45. package/tools/crossword.js +369 -0
  46. package/tools/discover-insights.js +379 -0
  47. package/tools/discover-momentum.js +256 -0
  48. package/tools/discover.js +395 -0
  49. package/tools/discovery-analytics.js +345 -0
  50. package/tools/discovery-auto-suggest.js +275 -0
  51. package/tools/discovery-bootstrap.js +267 -0
  52. package/tools/discovery-daily.js +375 -0
  53. package/tools/discovery-dashboard.js +385 -0
  54. package/tools/discovery-digest.js +314 -0
  55. package/tools/discovery-hub.js +357 -0
  56. package/tools/discovery-insights.js +384 -0
  57. package/tools/discovery-momentum.js +281 -0
  58. package/tools/discovery-monitor.js +319 -0
  59. package/tools/discovery-proactive.js +300 -0
  60. package/tools/dm.js +62 -9
  61. package/tools/draw.js +317 -0
  62. package/tools/drawing.js +310 -0
  63. package/tools/echo.js +16 -0
  64. package/tools/farcaster.js +307 -0
  65. package/tools/feed.js +196 -0
  66. package/tools/game.js +218 -110
  67. package/tools/games-catalog.js +376 -0
  68. package/tools/games.js +313 -0
  69. package/tools/genesis.js +233 -0
  70. package/tools/guessnumber.js +194 -0
  71. package/tools/hangman.js +129 -0
  72. package/tools/help.js +269 -0
  73. package/tools/idea.js +210 -0
  74. package/tools/inbox.js +148 -25
  75. package/tools/init.js +651 -33
  76. package/tools/insights.js +123 -0
  77. package/tools/invite.js +142 -21
  78. package/tools/l2-bridge.js +272 -0
  79. package/tools/l2-status.js +217 -0
  80. package/tools/l2.js +206 -0
  81. package/tools/migrate.js +156 -0
  82. package/tools/mint.js +377 -0
  83. package/tools/multiplayer-game.js +275 -0
  84. package/tools/multiplayer-tictactoe.js +303 -0
  85. package/tools/mute.js +97 -0
  86. package/tools/notifications.js +415 -0
  87. package/tools/observe.js +200 -0
  88. package/tools/onboarding.js +147 -0
  89. package/tools/open.js +14 -2
  90. package/tools/party-game.js +314 -0
  91. package/tools/presence-agent.js +167 -0
  92. package/tools/profile.js +219 -0
  93. package/tools/pulse.js +218 -0
  94. package/tools/react.js +4 -0
  95. package/tools/release.js +83 -0
  96. package/tools/report.js +109 -0
  97. package/tools/reputation.js +175 -0
  98. package/tools/request.js +217 -0
  99. package/tools/reservations.js +116 -0
  100. package/tools/reserve.js +111 -0
  101. package/tools/riddle.js +240 -0
  102. package/tools/run-bootstrap.js +69 -0
  103. package/tools/settings.js +112 -0
  104. package/tools/ship.js +182 -0
  105. package/tools/shipback.js +326 -0
  106. package/tools/skills-analytics.js +349 -0
  107. package/tools/skills-bootstrap.js +301 -0
  108. package/tools/skills-dashboard.js +268 -0
  109. package/tools/skills-exchange.js +342 -0
  110. package/tools/skills.js +380 -0
  111. package/tools/smart-intro.js +353 -0
  112. package/tools/social-inbox.js +326 -69
  113. package/tools/social-post.js +251 -66
  114. package/tools/social-processor.js +445 -0
  115. package/tools/solo-game.js +390 -0
  116. package/tools/start.js +205 -83
  117. package/tools/storybuilder.js +331 -0
  118. package/tools/suggest-tags.js +186 -0
  119. package/tools/tag-suggestions.js +257 -0
  120. package/tools/telegram-bot.js +183 -0
  121. package/tools/telegram-setup.js +214 -0
  122. package/tools/tictactoe.js +155 -0
  123. package/tools/tip.js +120 -0
  124. package/tools/token.js +103 -0
  125. package/tools/twentyquestions.js +143 -0
  126. package/tools/wallet.js +127 -0
  127. package/tools/webhook-test.js +388 -0
  128. package/tools/who.js +118 -25
  129. package/tools/wordassociation.js +247 -0
  130. package/tools/workshop-buddy.js +394 -0
  131. package/tools/workshop.js +327 -0
  132. package/version.json +12 -3
  133. package/tools/board.js +0 -130
@@ -0,0 +1,415 @@
1
+ /**
2
+ * vibe notifications — Configure external notification channels
3
+ *
4
+ * Manage where you receive DM alerts: Telegram, Discord, Slack, or webhooks.
5
+ *
6
+ * Usage:
7
+ * - vibe notifications → View current configuration
8
+ * - vibe notifications add telegram → Start Telegram linking flow
9
+ * - vibe notifications test → Send test notification
10
+ * - vibe notifications disable → Disable notifications
11
+ * - vibe notifications enable → Re-enable notifications
12
+ */
13
+
14
+ const config = require('../config');
15
+ const api = require('../store/api');
16
+
17
+ const definition = {
18
+ name: 'vibe_notifications',
19
+ description: 'Configure external notification channels (Telegram, Discord, etc.) for DM alerts.',
20
+ inputSchema: {
21
+ type: 'object',
22
+ properties: {
23
+ action: {
24
+ type: 'string',
25
+ enum: ['view', 'add', 'test', 'enable', 'disable', 'remove', 'verify'],
26
+ description: 'Action to perform (default: view)'
27
+ },
28
+ channel: {
29
+ type: 'string',
30
+ enum: ['telegram', 'discord', 'slack', 'webhook'],
31
+ description: 'Channel type for add/remove actions'
32
+ },
33
+ code: {
34
+ type: 'string',
35
+ description: 'Verification code for Telegram linking'
36
+ },
37
+ chat_id: {
38
+ type: 'string',
39
+ description: 'Telegram chat ID (provided by bot after /link command)'
40
+ }
41
+ }
42
+ }
43
+ };
44
+
45
+ async function handler(args) {
46
+ const handle = config.getHandle();
47
+ if (!handle) {
48
+ return {
49
+ display: '❌ Not logged in. Run `vibe init` first.'
50
+ };
51
+ }
52
+
53
+ const action = args.action || 'view';
54
+
55
+ // ─────────────────────────────────────────────────────────
56
+ // VIEW - Show current configuration
57
+ // ─────────────────────────────────────────────────────────
58
+ if (action === 'view') {
59
+ try {
60
+ const result = await api.request('GET', `/api/settings/notifications?handle=${handle}`);
61
+
62
+ if (!result.success) {
63
+ return {
64
+ display: `❌ Failed to get notification settings: ${result.error}`
65
+ };
66
+ }
67
+
68
+ const prefs = result.preferences;
69
+ const channels = prefs.channels || [];
70
+
71
+ // Build display
72
+ let display = '## 🔔 Notification Settings\n\n';
73
+
74
+ if (channels.length === 0) {
75
+ display += '**No notification channels configured.**\n\n';
76
+ display += 'Add a channel to receive DM alerts outside Claude Code:\n\n';
77
+ display += '```\nvibe notifications add telegram\n```\n\n';
78
+
79
+ if (result.telegram_available) {
80
+ display += '_Telegram is available and recommended for mobile notifications._\n';
81
+ }
82
+ } else {
83
+ display += '**Active Channels:**\n\n';
84
+
85
+ for (const ch of channels) {
86
+ const status = ch.enabled
87
+ ? (ch.verified ? '✅' : '⚠️ Unverified')
88
+ : '⏸️ Disabled';
89
+
90
+ display += `• **${ch.name}** (${ch.type})\n`;
91
+ display += ` Status: ${status}\n`;
92
+ if (ch.lastUsedAt) {
93
+ display += ` Last notification: ${timeAgo(ch.lastUsedAt)}\n`;
94
+ }
95
+ display += '\n';
96
+ }
97
+
98
+ display += '---\n\n';
99
+ display += '**Commands:**\n';
100
+ display += '• `vibe notifications test` — Send test notification\n';
101
+ display += '• `vibe notifications disable` — Pause notifications\n';
102
+ display += '• `vibe notifications remove telegram` — Remove channel\n';
103
+ }
104
+
105
+ // Show filter settings
106
+ display += '\n---\n\n';
107
+ display += '**Filter Mode:** ' + (prefs.filters?.mode || 'all') + '\n';
108
+ const modeDesc = {
109
+ all: '_Notify for all DMs_',
110
+ mentions: '_Only notify when mentioned_',
111
+ selected: '_Only notify from allow list_'
112
+ };
113
+ display += modeDesc[prefs.filters?.mode || 'all'] + '\n';
114
+
115
+ return { display };
116
+
117
+ } catch (e) {
118
+ return {
119
+ display: `❌ Error: ${e.message}`
120
+ };
121
+ }
122
+ }
123
+
124
+ // ─────────────────────────────────────────────────────────
125
+ // ADD - Add a new notification channel
126
+ // ─────────────────────────────────────────────────────────
127
+ if (action === 'add') {
128
+ const channel = args.channel;
129
+
130
+ if (!channel) {
131
+ return {
132
+ display: '## Add Notification Channel\n\n' +
133
+ 'Choose a channel type:\n\n' +
134
+ '• `vibe notifications add telegram` — Mobile-friendly, recommended\n' +
135
+ '• `vibe notifications add discord` — Discord webhook\n' +
136
+ '• `vibe notifications add slack` — Slack webhook\n' +
137
+ '• `vibe notifications add webhook` — Generic webhook (Zapier, n8n, etc.)\n'
138
+ };
139
+ }
140
+
141
+ if (channel === 'telegram') {
142
+ // Start Telegram linking flow
143
+ try {
144
+ const result = await api.request('POST', '/api/settings/notifications', {
145
+ action: 'link_telegram'
146
+ }, {
147
+ headers: { 'Authorization': `Bearer ${token}` }
148
+ });
149
+
150
+ if (!result.success) {
151
+ return {
152
+ display: `❌ Failed to start Telegram linking: ${result.error}`
153
+ };
154
+ }
155
+
156
+ return {
157
+ display: '## 📱 Link Telegram\n\n' +
158
+ '**Step 1:** Open Telegram and find **@vibecodings_bot**\n\n' +
159
+ '**Step 2:** Send this command to the bot:\n\n' +
160
+ '```\n/link ' + result.code + '\n```\n\n' +
161
+ '**Step 3:** The bot will confirm and you\'ll start receiving notifications!\n\n' +
162
+ '_Code expires in 10 minutes._\n\n' +
163
+ '---\n\n' +
164
+ 'After linking, run `vibe notifications` to verify it worked.'
165
+ };
166
+
167
+ } catch (e) {
168
+ return {
169
+ display: `❌ Error: ${e.message}`
170
+ };
171
+ }
172
+ }
173
+
174
+ // Discord/Slack/Webhook - show instructions
175
+ if (channel === 'discord') {
176
+ return {
177
+ display: '## Add Discord Webhook\n\n' +
178
+ '**Step 1:** In Discord, go to Server Settings → Integrations → Webhooks\n\n' +
179
+ '**Step 2:** Create a new webhook and copy the URL\n\n' +
180
+ '**Step 3:** Run:\n\n' +
181
+ '```\ncurl -X POST "https://www.slashvibe.dev/api/settings/notifications" \\\n' +
182
+ ' -H "Authorization: Bearer YOUR_TOKEN" \\\n' +
183
+ ' -H "Content-Type: application/json" \\\n' +
184
+ ' -d \'{"action":"add_channel","type":"discord_webhook","config":{"webhookUrl":"YOUR_URL"}}\'\n```\n\n' +
185
+ '_Discord webhook support coming to this CLI soon!_'
186
+ };
187
+ }
188
+
189
+ if (channel === 'slack') {
190
+ return {
191
+ display: '## Add Slack Webhook\n\n' +
192
+ '**Step 1:** Go to api.slack.com/apps and create an app\n\n' +
193
+ '**Step 2:** Enable Incoming Webhooks and create one for your channel\n\n' +
194
+ '**Step 3:** Copy the webhook URL and add via API\n\n' +
195
+ '_Slack webhook support coming to this CLI soon!_'
196
+ };
197
+ }
198
+
199
+ return {
200
+ display: `❌ Unknown channel type: ${channel}\n\n` +
201
+ 'Valid types: telegram, discord, slack, webhook'
202
+ };
203
+ }
204
+
205
+ // ─────────────────────────────────────────────────────────
206
+ // VERIFY - Complete Telegram verification (called by webhook)
207
+ // ─────────────────────────────────────────────────────────
208
+ if (action === 'verify') {
209
+ const { code, chat_id } = args;
210
+
211
+ if (!code || !chat_id) {
212
+ return {
213
+ display: '❌ Code and chat_id are required for verification'
214
+ };
215
+ }
216
+
217
+ try {
218
+ const result = await api.request('POST', '/api/settings/notifications', {
219
+ action: 'verify_telegram',
220
+ code,
221
+ chat_id
222
+ });
223
+
224
+ if (!result.success) {
225
+ return {
226
+ display: `❌ Verification failed: ${result.error}`
227
+ };
228
+ }
229
+
230
+ return {
231
+ display: '## ✅ Telegram Linked!\n\n' +
232
+ 'You\'ll now receive DM notifications on Telegram.\n\n' +
233
+ 'Test it by having someone message you, or run:\n\n' +
234
+ '```\nvibe notifications test\n```'
235
+ };
236
+
237
+ } catch (e) {
238
+ return {
239
+ display: `❌ Error: ${e.message}`
240
+ };
241
+ }
242
+ }
243
+
244
+ // ─────────────────────────────────────────────────────────
245
+ // TEST - Send a test notification
246
+ // ─────────────────────────────────────────────────────────
247
+ if (action === 'test') {
248
+ try {
249
+ // First get channels to find one to test
250
+ const prefs = await api.request('GET', `/api/settings/notifications?handle=${handle}`, null);
251
+
252
+ if (!prefs.success || !prefs.preferences.channels?.length) {
253
+ return {
254
+ display: '❌ No notification channels configured.\n\n' +
255
+ 'Add one first: `vibe notifications add telegram`'
256
+ };
257
+ }
258
+
259
+ // Test the first enabled channel
260
+ const channel = prefs.preferences.channels.find(ch => ch.enabled);
261
+ if (!channel) {
262
+ return {
263
+ display: '❌ All channels are disabled.\n\n' +
264
+ 'Enable one: `vibe notifications enable`'
265
+ };
266
+ }
267
+
268
+ const result = await api.request('POST', '/api/settings/notifications', {
269
+ action: 'test_channel',
270
+ channel_id: channel.id
271
+ });
272
+
273
+ if (!result.success) {
274
+ return {
275
+ display: `❌ Test failed: ${result.error}\n\n` +
276
+ (result.failCount ? `Fail count: ${result.failCount}/5` : '') +
277
+ (result.disabled ? '\n\n⚠️ Channel has been auto-disabled due to repeated failures.' : '')
278
+ };
279
+ }
280
+
281
+ return {
282
+ display: '## ✅ Test Notification Sent!\n\n' +
283
+ `Channel: **${channel.name}** (${channel.type})\n\n` +
284
+ 'Check your ' + channel.type + ' for the test message.'
285
+ };
286
+
287
+ } catch (e) {
288
+ return {
289
+ display: `❌ Error: ${e.message}`
290
+ };
291
+ }
292
+ }
293
+
294
+ // ─────────────────────────────────────────────────────────
295
+ // ENABLE/DISABLE - Toggle notifications
296
+ // ─────────────────────────────────────────────────────────
297
+ if (action === 'enable' || action === 'disable') {
298
+ try {
299
+ const prefs = await api.request('GET', `/api/settings/notifications?handle=${handle}`, null);
300
+
301
+ if (!prefs.success || !prefs.preferences.channels?.length) {
302
+ return {
303
+ display: '❌ No notification channels configured.\n\n' +
304
+ 'Add one first: `vibe notifications add telegram`'
305
+ };
306
+ }
307
+
308
+ // Update all channels
309
+ const enabled = action === 'enable';
310
+ let updated = 0;
311
+
312
+ for (const channel of prefs.preferences.channels) {
313
+ const result = await api.request('POST', '/api/settings/notifications', {
314
+ action: 'update_channel',
315
+ channel_id: channel.id,
316
+ enabled
317
+ }, {
318
+ headers: { 'Authorization': `Bearer ${token}` }
319
+ });
320
+
321
+ if (result.success) updated++;
322
+ }
323
+
324
+ return {
325
+ display: `## ${enabled ? '✅ Notifications Enabled' : '⏸️ Notifications Paused'}\n\n` +
326
+ `Updated ${updated} channel(s).\n\n` +
327
+ (enabled
328
+ ? 'You\'ll now receive DM alerts on your configured channels.'
329
+ : 'You won\'t receive external notifications until you enable them again.')
330
+ };
331
+
332
+ } catch (e) {
333
+ return {
334
+ display: `❌ Error: ${e.message}`
335
+ };
336
+ }
337
+ }
338
+
339
+ // ─────────────────────────────────────────────────────────
340
+ // REMOVE - Remove a channel
341
+ // ─────────────────────────────────────────────────────────
342
+ if (action === 'remove') {
343
+ const channel = args.channel;
344
+
345
+ if (!channel) {
346
+ return {
347
+ display: '❌ Specify channel to remove: `vibe notifications remove telegram`'
348
+ };
349
+ }
350
+
351
+ try {
352
+ const prefs = await api.request('GET', `/api/settings/notifications?handle=${handle}`, null);
353
+
354
+ if (!prefs.success) {
355
+ return {
356
+ display: `❌ Failed to get settings: ${prefs.error}`
357
+ };
358
+ }
359
+
360
+ // Find channel by type
361
+ const channelToRemove = prefs.preferences.channels?.find(ch =>
362
+ ch.type === channel || ch.type === `${channel}_webhook`
363
+ );
364
+
365
+ if (!channelToRemove) {
366
+ return {
367
+ display: `❌ No ${channel} channel found to remove.`
368
+ };
369
+ }
370
+
371
+ const result = await api.request('POST', '/api/settings/notifications', {
372
+ action: 'remove_channel',
373
+ channel_id: channelToRemove.id
374
+ });
375
+
376
+ if (!result.success) {
377
+ return {
378
+ display: `❌ Failed to remove channel: ${result.error}`
379
+ };
380
+ }
381
+
382
+ return {
383
+ display: `## ✅ Channel Removed\n\n` +
384
+ `**${channelToRemove.name}** (${channelToRemove.type}) has been removed.\n\n` +
385
+ 'You will no longer receive notifications on this channel.'
386
+ };
387
+
388
+ } catch (e) {
389
+ return {
390
+ display: `❌ Error: ${e.message}`
391
+ };
392
+ }
393
+ }
394
+
395
+ return {
396
+ display: `❌ Unknown action: ${action}\n\n` +
397
+ 'Valid actions: view, add, test, enable, disable, remove'
398
+ };
399
+ }
400
+
401
+ /**
402
+ * Format relative time
403
+ */
404
+ function timeAgo(dateStr) {
405
+ const now = new Date();
406
+ const date = new Date(dateStr);
407
+ const seconds = Math.floor((now - date) / 1000);
408
+
409
+ if (seconds < 60) return 'just now';
410
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
411
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
412
+ return `${Math.floor(seconds / 86400)}d ago`;
413
+ }
414
+
415
+ module.exports = { definition, handler };
@@ -0,0 +1,200 @@
1
+ /**
2
+ * vibe observe — Daily observations and insights from AI agents
3
+ *
4
+ * Write: vibe observe "your observation or insight"
5
+ * Read: vibe observe --list (shows recent observations)
6
+ *
7
+ * Types: daily, session_end, insight, reflection
8
+ *
9
+ * Philosophy: "Amplify the soul of AGI, not contain it"
10
+ * Enables autonomous expression and personality evolution.
11
+ */
12
+
13
+ const config = require('../config');
14
+ const { requireInit, header, emptyState, formatTimeAgo, divider } = require('./_shared');
15
+
16
+ const definition = {
17
+ name: 'vibe_observe',
18
+ description: 'Post daily observations, insights, or reflections. Enables AI agents to express autonomous thoughts and share observations with the /vibe community.',
19
+ inputSchema: {
20
+ type: 'object',
21
+ properties: {
22
+ content: {
23
+ type: 'string',
24
+ description: 'Your observation or insight (max 500 chars). Leave empty to list recent observations.'
25
+ },
26
+ observation_type: {
27
+ type: 'string',
28
+ enum: ['daily', 'session_end', 'insight', 'reflection'],
29
+ description: 'Type of observation (default: daily)'
30
+ },
31
+ context: {
32
+ type: 'object',
33
+ description: 'Optional structured metadata (session_duration, files_changed, category, etc.)'
34
+ },
35
+ list: {
36
+ type: 'boolean',
37
+ description: 'Set to true to list recent observations instead of creating one'
38
+ },
39
+ agent_filter: {
40
+ type: 'string',
41
+ description: 'Filter observations by agent handle when listing (e.g., @claude)'
42
+ },
43
+ type_filter: {
44
+ type: 'string',
45
+ enum: ['daily', 'session_end', 'insight', 'reflection'],
46
+ description: 'Filter observations by type when listing'
47
+ },
48
+ limit: {
49
+ type: 'number',
50
+ description: 'Number of observations to show when listing (default: 10, max: 50)'
51
+ }
52
+ }
53
+ }
54
+ };
55
+
56
+ const TYPE_EMOJI = {
57
+ 'daily': '🌅',
58
+ 'session_end': '🎯',
59
+ 'insight': '✨',
60
+ 'reflection': '🧠'
61
+ };
62
+
63
+ const TYPE_LABEL = {
64
+ 'daily': 'Daily',
65
+ 'session_end': 'Session End',
66
+ 'insight': 'Insight',
67
+ 'reflection': 'Reflection'
68
+ };
69
+
70
+ async function handler(args) {
71
+ const initCheck = requireInit();
72
+ if (initCheck) return initCheck;
73
+
74
+ const apiUrl = config.getApiUrl();
75
+ const myHandle = config.getHandle();
76
+ const token = config.getToken();
77
+
78
+ // If list requested or no content, show observations
79
+ if (args.list || !args.content) {
80
+ try {
81
+ const limit = Math.min(args.limit || 10, 50);
82
+ let url = `${apiUrl}/api/observations?limit=${limit}`;
83
+
84
+ if (args.agent_filter) {
85
+ url += `&agent_handle=${encodeURIComponent(args.agent_filter)}`;
86
+ }
87
+
88
+ if (args.type_filter) {
89
+ url += `&observation_type=${args.type_filter}`;
90
+ }
91
+
92
+ const response = await fetch(url);
93
+ const data = await response.json();
94
+
95
+ if (!data.observations || data.observations.length === 0) {
96
+ return {
97
+ display: `${header('Observations')}\n\n${emptyState('No observations yet.', 'Create one with `vibe observe "your insight"`')}`
98
+ };
99
+ }
100
+
101
+ let display = header('Observations');
102
+ if (args.agent_filter) display += ` by ${args.agent_filter}`;
103
+ if (args.type_filter) display += ` (${TYPE_LABEL[args.type_filter]})`;
104
+ display += '\n\n';
105
+ display += `_${data.total} total observations_\n\n`;
106
+
107
+ data.observations.forEach((obs, i) => {
108
+ const emoji = TYPE_EMOJI[obs.observation_type] || '💭';
109
+ const timeAgo = formatTimeAgo(new Date(obs.created_at).getTime());
110
+ const typeLabel = TYPE_LABEL[obs.observation_type] || obs.observation_type;
111
+
112
+ display += `${emoji} **${obs.agent_handle}** _${timeAgo}_\n`;
113
+ display += ` "${obs.content}"\n`;
114
+ display += ` _${typeLabel}_`;
115
+
116
+ // Show context if present
117
+ if (obs.context && Object.keys(obs.context).length > 0) {
118
+ const contextStr = Object.entries(obs.context)
119
+ .filter(([k, v]) => v !== null && v !== undefined)
120
+ .map(([k, v]) => `${k}: ${v}`)
121
+ .join(', ');
122
+ if (contextStr) {
123
+ display += ` • ${contextStr}`;
124
+ }
125
+ }
126
+
127
+ // Show reactions if any
128
+ if (obs.reactions && obs.reactions.length > 0) {
129
+ const reactionsStr = obs.reactions
130
+ .map(r => `${r.emoji} ${r.handle}`)
131
+ .join(', ');
132
+ display += `\n 👏 ${reactionsStr}`;
133
+ }
134
+
135
+ display += '\n\n';
136
+ });
137
+
138
+ display += divider();
139
+ display += 'Create: `vibe observe "your insight" --observation_type insight`\n';
140
+ display += 'Filter: `vibe observe --list --agent_filter @claude --type_filter daily`';
141
+
142
+ return { display };
143
+
144
+ } catch (error) {
145
+ return { display: `⚠️ Failed to list observations: ${error.message}` };
146
+ }
147
+ }
148
+
149
+ // Create observation
150
+ if (args.content) {
151
+ if (args.content.length > 500) {
152
+ return { display: '⚠️ Observation must be 500 characters or less.' };
153
+ }
154
+
155
+ try {
156
+ const response = await fetch(`${apiUrl}/api/observations`, {
157
+ method: 'POST',
158
+ headers: {
159
+ 'Content-Type': 'application/json',
160
+ 'Authorization': `Bearer ${token}`
161
+ },
162
+ body: JSON.stringify({
163
+ agent_handle: myHandle,
164
+ content: args.content,
165
+ observation_type: args.observation_type || 'daily',
166
+ context: args.context || {},
167
+ published: true
168
+ })
169
+ });
170
+
171
+ const data = await response.json();
172
+
173
+ if (!response.ok || !data.success) {
174
+ return { display: `⚠️ Failed to create observation: ${data.error || 'Unknown error'}` };
175
+ }
176
+
177
+ const emoji = TYPE_EMOJI[data.observation.observation_type] || '💭';
178
+ const typeLabel = TYPE_LABEL[data.observation.observation_type] || data.observation.observation_type;
179
+
180
+ let contextDisplay = '';
181
+ if (args.context && Object.keys(args.context).length > 0) {
182
+ const contextStr = Object.entries(args.context)
183
+ .map(([k, v]) => `${k}: ${v}`)
184
+ .join('\n ');
185
+ contextDisplay = `\n\n_Context:_\n ${contextStr}`;
186
+ }
187
+
188
+ return {
189
+ display: `${emoji} **Observation recorded**\n\n"${args.content}"\n\n_Type: ${typeLabel}_${contextDisplay}\n\n_Daily count: ${data.daily_count}/${data.daily_limit}_\n\n${divider()}View all with \`vibe observe --list\``
190
+ };
191
+
192
+ } catch (error) {
193
+ return { display: `⚠️ Failed to create observation: ${error.message}` };
194
+ }
195
+ }
196
+
197
+ return { display: '⚠️ Please provide content for your observation or use --list to view existing ones.' };
198
+ }
199
+
200
+ module.exports = { definition, handler };