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.
- package/LICENSE +21 -0
- package/README.md +58 -40
- package/config.js +171 -3
- package/index.js +136 -16
- package/intelligence/index.js +38 -0
- package/intelligence/infer.js +316 -0
- package/intelligence/patterns.js +651 -0
- package/intelligence/proactive.js +358 -0
- package/intelligence/serendipity.js +306 -0
- package/notify.js +141 -18
- package/package.json +8 -4
- package/presence.js +5 -1
- package/protocol/index.js +88 -1
- package/protocol/telegram-commands.js +199 -0
- package/store/api.js +360 -25
- package/store/index.js +7 -7
- package/store/local.js +67 -11
- package/store/profiles.js +287 -0
- package/store/reservations.js +321 -0
- package/store/skills.js +378 -0
- package/tools/_actions.js +270 -14
- package/tools/_connection-queue.js +257 -0
- package/tools/_discovery-enhanced.js +290 -0
- package/tools/_discovery.js +346 -0
- package/tools/_proactive-discovery.js +301 -0
- package/tools/admin-inbox.js +218 -0
- package/tools/agent-treasury.js +288 -0
- package/tools/agents.js +122 -0
- package/tools/arcade.js +173 -0
- package/tools/artifact-create.js +236 -0
- package/tools/artifact-view.js +174 -0
- package/tools/ask-expert.js +160 -0
- package/tools/auto-suggest-connections.js +304 -0
- package/tools/away.js +68 -0
- package/tools/back.js +51 -0
- package/tools/become-expert.js +150 -0
- package/tools/bootstrap-skills.js +231 -0
- package/tools/bridge-dashboard.js +342 -0
- package/tools/bridge-health.js +400 -0
- package/tools/bridge-live.js +384 -0
- package/tools/bridges.js +383 -0
- package/tools/bye.js +4 -0
- package/tools/collaborative-drawing.js +286 -0
- package/tools/colorguess.js +281 -0
- package/tools/crossword.js +369 -0
- package/tools/discover-insights.js +379 -0
- package/tools/discover-momentum.js +256 -0
- package/tools/discover.js +395 -0
- package/tools/discovery-analytics.js +345 -0
- package/tools/discovery-auto-suggest.js +275 -0
- package/tools/discovery-bootstrap.js +267 -0
- package/tools/discovery-daily.js +375 -0
- package/tools/discovery-dashboard.js +385 -0
- package/tools/discovery-digest.js +314 -0
- package/tools/discovery-hub.js +357 -0
- package/tools/discovery-insights.js +384 -0
- package/tools/discovery-momentum.js +281 -0
- package/tools/discovery-monitor.js +319 -0
- package/tools/discovery-proactive.js +300 -0
- package/tools/dm.js +62 -9
- package/tools/draw.js +317 -0
- package/tools/drawing.js +310 -0
- package/tools/echo.js +16 -0
- package/tools/farcaster.js +307 -0
- package/tools/feed.js +196 -0
- package/tools/game.js +218 -110
- package/tools/games-catalog.js +376 -0
- package/tools/games.js +313 -0
- package/tools/genesis.js +233 -0
- package/tools/guessnumber.js +194 -0
- package/tools/hangman.js +129 -0
- package/tools/help.js +269 -0
- package/tools/idea.js +210 -0
- package/tools/inbox.js +148 -25
- package/tools/init.js +651 -33
- package/tools/insights.js +123 -0
- package/tools/invite.js +142 -21
- package/tools/l2-bridge.js +272 -0
- package/tools/l2-status.js +217 -0
- package/tools/l2.js +206 -0
- package/tools/migrate.js +156 -0
- package/tools/mint.js +377 -0
- package/tools/multiplayer-game.js +275 -0
- package/tools/multiplayer-tictactoe.js +303 -0
- package/tools/mute.js +97 -0
- package/tools/notifications.js +415 -0
- package/tools/observe.js +200 -0
- package/tools/onboarding.js +147 -0
- package/tools/open.js +14 -2
- package/tools/party-game.js +314 -0
- package/tools/presence-agent.js +167 -0
- package/tools/profile.js +219 -0
- package/tools/pulse.js +218 -0
- package/tools/react.js +4 -0
- package/tools/release.js +83 -0
- package/tools/report.js +109 -0
- package/tools/reputation.js +175 -0
- package/tools/request.js +217 -0
- package/tools/reservations.js +116 -0
- package/tools/reserve.js +111 -0
- package/tools/riddle.js +240 -0
- package/tools/run-bootstrap.js +69 -0
- package/tools/settings.js +112 -0
- package/tools/ship.js +182 -0
- package/tools/shipback.js +326 -0
- package/tools/skills-analytics.js +349 -0
- package/tools/skills-bootstrap.js +301 -0
- package/tools/skills-dashboard.js +268 -0
- package/tools/skills-exchange.js +342 -0
- package/tools/skills.js +380 -0
- package/tools/smart-intro.js +353 -0
- package/tools/social-inbox.js +326 -69
- package/tools/social-post.js +251 -66
- package/tools/social-processor.js +445 -0
- package/tools/solo-game.js +390 -0
- package/tools/start.js +205 -83
- package/tools/storybuilder.js +331 -0
- package/tools/suggest-tags.js +186 -0
- package/tools/tag-suggestions.js +257 -0
- package/tools/telegram-bot.js +183 -0
- package/tools/telegram-setup.js +214 -0
- package/tools/tictactoe.js +155 -0
- package/tools/tip.js +120 -0
- package/tools/token.js +103 -0
- package/tools/twentyquestions.js +143 -0
- package/tools/wallet.js +127 -0
- package/tools/webhook-test.js +388 -0
- package/tools/who.js +118 -25
- package/tools/wordassociation.js +247 -0
- package/tools/workshop-buddy.js +394 -0
- package/tools/workshop.js +327 -0
- package/version.json +12 -3
- 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 };
|
package/tools/observe.js
ADDED
|
@@ -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 };
|