@jtalk22/slack-mcp 1.0.0

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.
@@ -0,0 +1,258 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Slack Web API</title>
7
+ <style>
8
+ * { box-sizing: border-box; margin: 0; padding: 0; }
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
+ background: #1a1a2e;
12
+ color: #eee;
13
+ min-height: 100vh;
14
+ padding: 20px;
15
+ }
16
+ .container { max-width: 1200px; margin: 0 auto; }
17
+ h1 { color: #4ecdc4; margin-bottom: 20px; }
18
+ .auth-section {
19
+ background: #16213e;
20
+ padding: 20px;
21
+ border-radius: 8px;
22
+ margin-bottom: 20px;
23
+ }
24
+ .auth-section input {
25
+ width: 300px;
26
+ padding: 10px;
27
+ border: none;
28
+ border-radius: 4px;
29
+ background: #0f3460;
30
+ color: #fff;
31
+ margin-right: 10px;
32
+ }
33
+ .auth-section button {
34
+ padding: 10px 20px;
35
+ background: #4ecdc4;
36
+ color: #1a1a2e;
37
+ border: none;
38
+ border-radius: 4px;
39
+ cursor: pointer;
40
+ font-weight: bold;
41
+ }
42
+ .auth-section button:hover { background: #45b7aa; }
43
+ .status { display: inline-block; margin-left: 15px; }
44
+ .status.ok { color: #4ecdc4; }
45
+ .status.error { color: #ff6b6b; }
46
+ .grid { display: grid; grid-template-columns: 300px 1fr; gap: 20px; }
47
+ .sidebar {
48
+ background: #16213e;
49
+ border-radius: 8px;
50
+ padding: 15px;
51
+ height: fit-content;
52
+ }
53
+ .sidebar h3 { color: #4ecdc4; margin-bottom: 15px; font-size: 14px; }
54
+ .conversation-list { list-style: none; }
55
+ .conversation-list li {
56
+ padding: 10px;
57
+ border-radius: 4px;
58
+ cursor: pointer;
59
+ margin-bottom: 5px;
60
+ background: #0f3460;
61
+ }
62
+ .conversation-list li:hover { background: #1a4a7a; }
63
+ .conversation-list li.active { background: #4ecdc4; color: #1a1a2e; }
64
+ .conversation-list .type { font-size: 11px; opacity: 0.7; }
65
+ .main-panel {
66
+ background: #16213e;
67
+ border-radius: 8px;
68
+ padding: 20px;
69
+ min-height: 500px;
70
+ }
71
+ .main-panel h2 { color: #4ecdc4; margin-bottom: 15px; }
72
+ .messages { max-height: 400px; overflow-y: auto; margin-bottom: 15px; }
73
+ .message {
74
+ background: #0f3460;
75
+ padding: 12px;
76
+ border-radius: 8px;
77
+ margin-bottom: 10px;
78
+ }
79
+ .message .header { display: flex; justify-content: space-between; margin-bottom: 8px; }
80
+ .message .user { font-weight: bold; color: #4ecdc4; }
81
+ .message .time { font-size: 12px; opacity: 0.6; }
82
+ .message .text { line-height: 1.5; white-space: pre-wrap; }
83
+ .send-box { display: flex; gap: 10px; }
84
+ .send-box input {
85
+ flex: 1;
86
+ padding: 12px;
87
+ border: none;
88
+ border-radius: 4px;
89
+ background: #0f3460;
90
+ color: #fff;
91
+ }
92
+ .send-box button {
93
+ padding: 12px 24px;
94
+ background: #4ecdc4;
95
+ color: #1a1a2e;
96
+ border: none;
97
+ border-radius: 4px;
98
+ cursor: pointer;
99
+ font-weight: bold;
100
+ }
101
+ .search-section { margin-bottom: 20px; display: flex; gap: 10px; }
102
+ .search-section input {
103
+ flex: 1;
104
+ padding: 10px;
105
+ border: none;
106
+ border-radius: 4px;
107
+ background: #0f3460;
108
+ color: #fff;
109
+ }
110
+ .search-section button {
111
+ padding: 10px 20px;
112
+ background: #e94560;
113
+ color: #fff;
114
+ border: none;
115
+ border-radius: 4px;
116
+ cursor: pointer;
117
+ }
118
+ .loading { text-align: center; padding: 40px; opacity: 0.6; }
119
+ .error-msg { background: #ff6b6b22; color: #ff6b6b; padding: 15px; border-radius: 8px; }
120
+ .tabs { display: flex; gap: 5px; margin-bottom: 15px; }
121
+ .tabs button {
122
+ padding: 8px 16px;
123
+ background: #0f3460;
124
+ color: #fff;
125
+ border: none;
126
+ border-radius: 4px;
127
+ cursor: pointer;
128
+ }
129
+ .tabs button.active { background: #4ecdc4; color: #1a1a2e; }
130
+ </style>
131
+ </head>
132
+ <body>
133
+ <div class="container">
134
+ <h1>Slack Web API</h1>
135
+ <div class="auth-section">
136
+ <input type="text" id="apiKey" placeholder="API Key (default: slack-mcp-local)">
137
+ <button onclick="connect()">Connect</button>
138
+ <span id="status" class="status"></span>
139
+ </div>
140
+ <div class="grid">
141
+ <div class="sidebar">
142
+ <h3>CONVERSATIONS</h3>
143
+ <div class="tabs">
144
+ <button class="active" onclick="loadConversations('im,mpim')">DMs</button>
145
+ <button onclick="loadConversations('public_channel,private_channel')">Channels</button>
146
+ </div>
147
+ <ul id="conversationList" class="conversation-list">
148
+ <li class="loading">Enter API key to connect</li>
149
+ </ul>
150
+ </div>
151
+ <div class="main-panel">
152
+ <div class="search-section">
153
+ <input type="text" id="searchQuery" placeholder="Search messages...">
154
+ <button onclick="searchMessages()">Search</button>
155
+ </div>
156
+ <h2 id="channelName">Select a conversation</h2>
157
+ <div id="messages" class="messages">
158
+ <div class="loading">Messages will appear here</div>
159
+ </div>
160
+ <div class="send-box">
161
+ <input type="text" id="messageInput" placeholder="Type a message..." onkeypress="if(event.key==='Enter')sendMessage()">
162
+ <button onclick="sendMessage()">Send</button>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ <script>
168
+ // Default API key matches server default - auto-connects on first visit
169
+ const DEFAULT_KEY = 'slack-mcp-local';
170
+ let apiKey = localStorage.getItem('slackApiKey') || DEFAULT_KEY;
171
+ let currentChannel = null;
172
+
173
+ document.getElementById('apiKey').value = apiKey;
174
+ setTimeout(connect, 100);
175
+ async function api(endpoint, options = {}) {
176
+ const res = await fetch(endpoint, {
177
+ ...options,
178
+ headers: { 'Authorization': 'Bearer ' + apiKey, 'Content-Type': 'application/json', ...options.headers }
179
+ });
180
+ if (!res.ok) { const err = await res.json(); throw new Error(err.error || 'API error'); }
181
+ return res.json();
182
+ }
183
+ async function connect() {
184
+ apiKey = document.getElementById('apiKey').value.trim() || DEFAULT_KEY;
185
+ localStorage.setItem('slackApiKey', apiKey);
186
+ const status = document.getElementById('status');
187
+ try {
188
+ const health = await api('/health');
189
+ status.textContent = 'Connected as ' + health.user + ' @ ' + health.team;
190
+ status.className = 'status ok';
191
+ loadConversations('im,mpim');
192
+ } catch (e) {
193
+ status.textContent = 'Error: ' + e.message;
194
+ status.className = 'status error';
195
+ }
196
+ }
197
+ async function loadConversations(types) {
198
+ const list = document.getElementById('conversationList');
199
+ list.innerHTML = '<li class="loading">Loading...</li>';
200
+ document.querySelectorAll('.tabs button').forEach(b => b.classList.remove('active'));
201
+ if (event && event.target) event.target.classList.add('active');
202
+ try {
203
+ const data = await api('/conversations?types=' + types);
204
+ list.innerHTML = data.conversations.map(c =>
205
+ '<li onclick="loadHistory(\'' + c.id + '\', \'' + c.name.replace(/'/g, "\\'") + '\')"><div>' + c.name + '</div><div class="type">' + c.type + '</div></li>'
206
+ ).join('');
207
+ } catch (e) { list.innerHTML = '<li class="error-msg">' + e.message + '</li>'; }
208
+ }
209
+ async function loadHistory(channelId, name) {
210
+ currentChannel = channelId;
211
+ document.getElementById('channelName').textContent = name;
212
+ document.querySelectorAll('.conversation-list li').forEach(li => li.classList.remove('active'));
213
+ event.target.closest('li').classList.add('active');
214
+ const container = document.getElementById('messages');
215
+ container.innerHTML = '<div class="loading">Loading messages...</div>';
216
+ try {
217
+ const data = await api('/conversations/' + channelId + '/history?limit=50');
218
+ renderMessages(data.messages);
219
+ } catch (e) { container.innerHTML = '<div class="error-msg">' + e.message + '</div>'; }
220
+ }
221
+ function renderMessages(messages) {
222
+ const container = document.getElementById('messages');
223
+ if (!messages || messages.length === 0) { container.innerHTML = '<div class="loading">No messages</div>'; return; }
224
+ container.innerHTML = messages.map(m =>
225
+ '<div class="message"><div class="header"><span class="user">' + (m.user || m.user_id || 'Unknown') + '</span><span class="time">' + new Date(m.datetime).toLocaleString() + '</span></div><div class="text">' + escapeHtml(m.text || '') + '</div></div>'
226
+ ).join('');
227
+ container.scrollTop = container.scrollHeight;
228
+ }
229
+ function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }
230
+ async function sendMessage() {
231
+ if (!currentChannel) { alert('Select a conversation first'); return; }
232
+ const input = document.getElementById('messageInput');
233
+ const text = input.value.trim();
234
+ if (!text) return;
235
+ try {
236
+ await api('/messages', { method: 'POST', body: JSON.stringify({ channel_id: currentChannel, text: text }) });
237
+ input.value = '';
238
+ loadHistory(currentChannel, document.getElementById('channelName').textContent);
239
+ } catch (e) { alert('Error: ' + e.message); }
240
+ }
241
+ async function searchMessages() {
242
+ const query = document.getElementById('searchQuery').value.trim();
243
+ if (!query) return;
244
+ document.getElementById('channelName').textContent = 'Search: "' + query + '"';
245
+ const container = document.getElementById('messages');
246
+ container.innerHTML = '<div class="loading">Searching...</div>';
247
+ try {
248
+ const data = await api('/search?q=' + encodeURIComponent(query));
249
+ if (data.matches && data.matches.length > 0) {
250
+ container.innerHTML = data.matches.map(m =>
251
+ '<div class="message"><div class="header"><span class="user">' + (m.user || 'Unknown') + ' in #' + m.channel + '</span><span class="time">' + new Date(m.datetime).toLocaleString() + '</span></div><div class="text">' + escapeHtml(m.text || '') + '</div></div>'
252
+ ).join('');
253
+ } else { container.innerHTML = '<div class="loading">No results found</div>'; }
254
+ } catch (e) { container.innerHTML = '<div class="error-msg">' + e.message + '</div>'; }
255
+ }
256
+ </script>
257
+ </body>
258
+ </html>
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Screenshot capture script using Playwright
4
+ * Captures polished screenshots of the demo UI for README/docs
5
+ */
6
+
7
+ import { chromium } from 'playwright';
8
+ import { fileURLToPath } from 'url';
9
+ import { dirname, join } from 'path';
10
+ import { readFileSync } from 'fs';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ const projectRoot = join(__dirname, '..');
15
+
16
+ async function captureScreenshots() {
17
+ console.log('Launching browser...');
18
+
19
+ const browser = await chromium.launch({
20
+ headless: true
21
+ });
22
+
23
+ const context = await browser.newContext({
24
+ viewport: { width: 1400, height: 900 },
25
+ deviceScaleFactor: 2, // Retina quality
26
+ colorScheme: 'dark'
27
+ });
28
+
29
+ const page = await context.newPage();
30
+
31
+ // Load the demo.html file directly
32
+ const demoPath = join(projectRoot, 'public', 'demo.html');
33
+ const demoHtml = readFileSync(demoPath, 'utf-8');
34
+
35
+ // Serve it as a data URL or file URL
36
+ await page.goto(`file://${demoPath}`);
37
+
38
+ // Wait for content to render
39
+ await page.waitForTimeout(1000);
40
+
41
+ const imagesDir = join(projectRoot, 'docs', 'images');
42
+
43
+ // Screenshot 1: Full UI with DMs
44
+ console.log('Capturing main UI screenshot...');
45
+ await page.screenshot({
46
+ path: join(imagesDir, 'demo-main.png'),
47
+ clip: { x: 0, y: 0, width: 1400, height: 800 }
48
+ });
49
+
50
+ // Screenshot 2: Conversation view (zoomed)
51
+ console.log('Capturing conversation screenshot...');
52
+ const mainPanel = await page.$('.main-panel');
53
+ if (mainPanel) {
54
+ await mainPanel.screenshot({
55
+ path: join(imagesDir, 'demo-messages.png')
56
+ });
57
+ }
58
+
59
+ // Screenshot 3: Sidebar with conversations
60
+ console.log('Capturing sidebar screenshot...');
61
+ const sidebar = await page.$('.sidebar');
62
+ if (sidebar) {
63
+ await sidebar.screenshot({
64
+ path: join(imagesDir, 'demo-sidebar.png')
65
+ });
66
+ }
67
+
68
+ // Screenshot 4: Switch to channels view
69
+ console.log('Capturing channels view...');
70
+ await page.click('.tabs button:nth-child(2)'); // Click Channels tab
71
+ await page.waitForTimeout(300);
72
+ await page.screenshot({
73
+ path: join(imagesDir, 'demo-channels.png'),
74
+ clip: { x: 0, y: 0, width: 1400, height: 800 }
75
+ });
76
+
77
+ // Screenshot 5: Engineering channel messages
78
+ console.log('Capturing channel messages...');
79
+ await page.click('.conversation-item:first-child'); // Click first channel
80
+ await page.waitForTimeout(300);
81
+ await page.screenshot({
82
+ path: join(imagesDir, 'demo-channel-messages.png'),
83
+ clip: { x: 0, y: 0, width: 1400, height: 800 }
84
+ });
85
+
86
+ await browser.close();
87
+
88
+ console.log('\nScreenshots saved to docs/images/');
89
+ console.log(' - demo-main.png');
90
+ console.log(' - demo-messages.png');
91
+ console.log(' - demo-sidebar.png');
92
+ console.log(' - demo-channels.png');
93
+ console.log(' - demo-channel-messages.png');
94
+ }
95
+
96
+ captureScreenshots().catch(console.error);
@@ -0,0 +1,37 @@
1
+ #!/bin/bash
2
+ # Sync improvements from private to public and prepare for publishing
3
+ # Usage: ./scripts/publish-public.sh
4
+
5
+ PRIVATE="$HOME/slack-mcp-server"
6
+ PUBLIC="$HOME/slack-mcp-server-public"
7
+
8
+ echo "Syncing improvements from private → public..."
9
+ echo ""
10
+
11
+ # Files to sync (code only, not configs)
12
+ FILES_TO_SYNC=(
13
+ "lib/handlers.js"
14
+ "lib/slack-client.js"
15
+ "lib/token-store.js"
16
+ "lib/tools.js"
17
+ "scripts/token-cli.js"
18
+ )
19
+
20
+ for file in "${FILES_TO_SYNC[@]}"; do
21
+ if [[ -f "$PRIVATE/$file" ]]; then
22
+ echo " → $file"
23
+ cp "$PRIVATE/$file" "$PUBLIC/$file"
24
+ fi
25
+ done
26
+
27
+ echo ""
28
+ echo "Synced core files. Review changes:"
29
+ echo ""
30
+ cd "$PUBLIC" && git status
31
+
32
+ echo ""
33
+ echo "Next steps:"
34
+ echo " 1. Review: cd ~/slack-mcp-server-public && git diff"
35
+ echo " 2. Commit: git add -A && git commit -m 'Your message'"
36
+ echo " 3. Push: git push origin main"
37
+ echo " 4. npm: npm publish (optional)"
@@ -0,0 +1,33 @@
1
+ #!/bin/bash
2
+ # Restore local slack-mcp-server from OneDrive backup
3
+ # Usage: ./scripts/sync-from-onedrive.sh [private|public|both]
4
+
5
+ ONEDRIVE_BASE="$HOME/Library/CloudStorage/OneDrive-Personal/Development/slack-mcp-server"
6
+ TARGET="${1:-both}"
7
+
8
+ echo "Restoring Slack MCP Server from OneDrive..."
9
+
10
+ if [[ "$TARGET" == "private" || "$TARGET" == "both" ]]; then
11
+ echo " → Restoring private version..."
12
+ rsync -av --delete \
13
+ --exclude 'node_modules' \
14
+ --exclude '.git' \
15
+ "$ONEDRIVE_BASE/private/" \
16
+ "$HOME/slack-mcp-server/"
17
+ echo " → Installing dependencies..."
18
+ cd "$HOME/slack-mcp-server" && npm install
19
+ fi
20
+
21
+ if [[ "$TARGET" == "public" || "$TARGET" == "both" ]]; then
22
+ echo " → Restoring public version..."
23
+ rsync -av --delete \
24
+ --exclude 'node_modules' \
25
+ --exclude '.git' \
26
+ "$ONEDRIVE_BASE/public/" \
27
+ "$HOME/slack-mcp-server-public/"
28
+ echo " → Installing dependencies..."
29
+ cd "$HOME/slack-mcp-server-public" && npm install
30
+ fi
31
+
32
+ echo ""
33
+ echo "Done! Restored from: $ONEDRIVE_BASE"
@@ -0,0 +1,31 @@
1
+ #!/bin/bash
2
+ # Sync local slack-mcp-server to OneDrive backup
3
+ # Usage: ./scripts/sync-to-onedrive.sh
4
+
5
+ ONEDRIVE_BASE="$HOME/Library/CloudStorage/OneDrive-Personal/Development/slack-mcp-server"
6
+
7
+ echo "Syncing Slack MCP Server to OneDrive..."
8
+
9
+ # Sync private version (exclude node_modules and .git)
10
+ echo " → Syncing private version..."
11
+ rsync -av --delete \
12
+ --exclude 'node_modules' \
13
+ --exclude '.git' \
14
+ --exclude '*.log' \
15
+ "$HOME/slack-mcp-server/" \
16
+ "$ONEDRIVE_BASE/private/"
17
+
18
+ # Sync public version (exclude node_modules and .git)
19
+ echo " → Syncing public version..."
20
+ rsync -av --delete \
21
+ --exclude 'node_modules' \
22
+ --exclude '.git' \
23
+ --exclude '*.log' \
24
+ "$HOME/slack-mcp-server-public/" \
25
+ "$ONEDRIVE_BASE/public/"
26
+
27
+ echo ""
28
+ echo "Done! Synced to: $ONEDRIVE_BASE"
29
+ echo ""
30
+ echo "Contents:"
31
+ ls -la "$ONEDRIVE_BASE"
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Token CLI - Manage Slack tokens
4
+ */
5
+
6
+ import { loadTokens, saveTokens, extractFromChrome, getFromFile, TOKEN_FILE } from "../lib/token-store.js";
7
+ import { slackAPI } from "../lib/slack-client.js";
8
+ import * as readline from "readline";
9
+
10
+ const command = process.argv[2];
11
+
12
+ async function main() {
13
+ switch (command) {
14
+ case "status":
15
+ await showStatus();
16
+ break;
17
+ case "refresh":
18
+ await manualRefresh();
19
+ break;
20
+ case "auto":
21
+ await autoExtract();
22
+ break;
23
+ case "clear":
24
+ await clearTokens();
25
+ break;
26
+ default:
27
+ console.log("Usage: token-cli.js <command>");
28
+ console.log("");
29
+ console.log("Commands:");
30
+ console.log(" status Check current token status");
31
+ console.log(" refresh Manually enter new tokens");
32
+ console.log(" auto Auto-extract from Chrome");
33
+ console.log(" clear Remove all stored tokens");
34
+ }
35
+ }
36
+
37
+ async function showStatus() {
38
+ const creds = loadTokens();
39
+ if (!creds) {
40
+ console.log("No tokens found");
41
+ console.log("");
42
+ console.log("Run one of:");
43
+ console.log(" npm run tokens:auto (with Slack open in Chrome)");
44
+ console.log(" npm run tokens:refresh (manual entry)");
45
+ return;
46
+ }
47
+
48
+ console.log("Token source:", creds.source);
49
+ console.log("Token file:", TOKEN_FILE);
50
+ console.log("");
51
+
52
+ try {
53
+ const result = await slackAPI("auth.test", {});
54
+ console.log("Status: VALID");
55
+ console.log("User:", result.user);
56
+ console.log("Team:", result.team);
57
+ console.log("User ID:", result.user_id);
58
+ } catch (e) {
59
+ console.log("Status: INVALID");
60
+ console.log("Error:", e.message);
61
+ }
62
+ }
63
+
64
+ async function manualRefresh() {
65
+ const rl = readline.createInterface({
66
+ input: process.stdin,
67
+ output: process.stdout
68
+ });
69
+
70
+ console.log("Manual Token Entry");
71
+ console.log("==================");
72
+ console.log("");
73
+ console.log("Get tokens from Chrome DevTools:");
74
+ console.log("1. Open https://app.slack.com");
75
+ console.log("2. F12 → Application → Cookies → find 'd' cookie");
76
+ console.log("3. F12 → Console → run:");
77
+ console.log(" JSON.parse(localStorage.localConfig_v2).teams[Object.keys(JSON.parse(localStorage.localConfig_v2).teams)[0]].token");
78
+ console.log("");
79
+
80
+ const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
81
+
82
+ const token = await question("Enter XOXC token: ");
83
+ const cookie = await question("Enter XOXD cookie: ");
84
+
85
+ rl.close();
86
+
87
+ if (!token.startsWith("xoxc-") || !cookie.startsWith("xoxd-")) {
88
+ console.log("");
89
+ console.log("Invalid tokens. Token should start with xoxc-, cookie with xoxd-");
90
+ process.exit(1);
91
+ }
92
+
93
+ saveTokens(token, cookie);
94
+ console.log("");
95
+ console.log("Tokens saved!");
96
+
97
+ try {
98
+ const result = await slackAPI("auth.test", {});
99
+ console.log("Verified as:", result.user, "@", result.team);
100
+ } catch (e) {
101
+ console.log("Warning: Tokens may be invalid:", e.message);
102
+ }
103
+ }
104
+
105
+ async function autoExtract() {
106
+ console.log("Attempting Chrome auto-extraction...");
107
+ console.log("");
108
+ console.log("Make sure:");
109
+ console.log(" - Chrome is running");
110
+ console.log(" - Slack tab is open (app.slack.com)");
111
+ console.log(" - You're logged in");
112
+ console.log("");
113
+
114
+ const tokens = extractFromChrome();
115
+ if (!tokens) {
116
+ console.log("Failed to extract tokens from Chrome.");
117
+ console.log("Try manual entry: npm run tokens:refresh");
118
+ process.exit(1);
119
+ }
120
+
121
+ saveTokens(tokens.token, tokens.cookie);
122
+ console.log("Tokens extracted and saved!");
123
+
124
+ try {
125
+ const result = await slackAPI("auth.test", {});
126
+ console.log("Verified as:", result.user, "@", result.team);
127
+ } catch (e) {
128
+ console.log("Warning: Tokens may be invalid:", e.message);
129
+ }
130
+ }
131
+
132
+ async function clearTokens() {
133
+ const fs = await import("fs");
134
+ const { execSync } = await import("child_process");
135
+
136
+ try {
137
+ fs.unlinkSync(TOKEN_FILE);
138
+ console.log("Deleted token file");
139
+ } catch (e) {
140
+ console.log("No token file to delete");
141
+ }
142
+
143
+ try {
144
+ execSync('security delete-generic-password -s "slack-mcp-server" -a "token" 2>/dev/null');
145
+ execSync('security delete-generic-password -s "slack-mcp-server" -a "cookie" 2>/dev/null');
146
+ console.log("Deleted keychain entries");
147
+ } catch (e) {
148
+ console.log("No keychain entries to delete");
149
+ }
150
+
151
+ console.log("All tokens cleared");
152
+ }
153
+
154
+ main().catch(e => {
155
+ console.error("Error:", e.message);
156
+ process.exit(1);
157
+ });