@jtalk22/slack-mcp 3.0.0 → 3.2.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.
Files changed (59) hide show
  1. package/README.md +63 -28
  2. package/docs/SETUP.md +64 -29
  3. package/docs/TROUBLESHOOTING.md +28 -0
  4. package/lib/handlers.js +156 -0
  5. package/lib/slack-client.js +11 -3
  6. package/lib/token-store.js +6 -5
  7. package/lib/tools.js +131 -0
  8. package/package.json +35 -36
  9. package/public/index.html +10 -6
  10. package/public/share.html +128 -0
  11. package/scripts/setup-wizard.js +1 -1
  12. package/server.json +10 -4
  13. package/src/server-http.js +16 -1
  14. package/src/server.js +31 -7
  15. package/src/web-server.js +119 -4
  16. package/docs/CLOUDFLARE-BROWSER-TOOLKIT.md +0 -67
  17. package/docs/COMMUNICATION-STYLE.md +0 -66
  18. package/docs/COMPATIBILITY.md +0 -19
  19. package/docs/DEPLOYMENT-MODES.md +0 -55
  20. package/docs/HN-LAUNCH.md +0 -72
  21. package/docs/INDEX.md +0 -40
  22. package/docs/INSTALL-PROOF.md +0 -18
  23. package/docs/LAUNCH-COPY-v3.0.0.md +0 -73
  24. package/docs/LAUNCH-MATRIX.md +0 -22
  25. package/docs/LAUNCH-OPS.md +0 -71
  26. package/docs/RELEASE-HEALTH.md +0 -90
  27. package/docs/SUPPORT-BOUNDARIES.md +0 -49
  28. package/docs/USE_CASE_RECIPES.md +0 -69
  29. package/docs/WEB-API.md +0 -303
  30. package/docs/images/demo-channel-messages.png +0 -0
  31. package/docs/images/demo-channels.png +0 -0
  32. package/docs/images/demo-claude-mobile-360x800.png +0 -0
  33. package/docs/images/demo-claude-mobile-390x844.png +0 -0
  34. package/docs/images/demo-main-mobile-360x800.png +0 -0
  35. package/docs/images/demo-main-mobile-390x844.png +0 -0
  36. package/docs/images/demo-main.png +0 -0
  37. package/docs/images/demo-messages.png +0 -0
  38. package/docs/images/demo-poster.png +0 -0
  39. package/docs/images/demo-sidebar.png +0 -0
  40. package/docs/images/diagram-oauth-comparison.svg +0 -80
  41. package/docs/images/diagram-session-flow.svg +0 -105
  42. package/docs/images/web-api-mobile-360x800.png +0 -0
  43. package/docs/images/web-api-mobile-390x844.png +0 -0
  44. package/public/demo-claude.html +0 -1958
  45. package/public/demo-video.html +0 -235
  46. package/public/demo.html +0 -1196
  47. package/scripts/build-release-health-delta.js +0 -201
  48. package/scripts/capture-screenshots.js +0 -146
  49. package/scripts/check-owner-attribution.sh +0 -80
  50. package/scripts/check-public-language.sh +0 -25
  51. package/scripts/check-version-parity.js +0 -176
  52. package/scripts/cloudflare-browser-tool.js +0 -237
  53. package/scripts/collect-release-health.js +0 -150
  54. package/scripts/record-demo.js +0 -162
  55. package/scripts/release-preflight.js +0 -243
  56. package/scripts/setup-git-hooks.sh +0 -15
  57. package/scripts/verify-core.js +0 -159
  58. package/scripts/verify-install-flow.js +0 -193
  59. package/scripts/verify-web.js +0 -269
@@ -1,237 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { writeFileSync } from "node:fs";
4
- import { resolve } from "node:path";
5
-
6
- const ACCOUNT_ID = process.env.CLOUDFLARE_ACCOUNT_ID;
7
- const API_TOKEN = process.env.CLOUDFLARE_API_TOKEN || process.env.CF_TERRAFORM_TOKEN;
8
-
9
- const ENDPOINTS = new Map([
10
- ["content", "content"],
11
- ["markdown", "markdown"],
12
- ["links", "links"],
13
- ["snapshot", "snapshot"],
14
- ["scrape", "scrape"],
15
- ["json", "json"],
16
- ["screenshot", "screenshot"],
17
- ["pdf", "pdf"],
18
- ]);
19
-
20
- function usage() {
21
- console.error(`Usage:
22
- node scripts/cloudflare-browser-tool.js verify
23
- node scripts/cloudflare-browser-tool.js <mode> <url> [options]
24
-
25
- Modes:
26
- content Rendered HTML
27
- markdown Rendered markdown
28
- links Extract links
29
- snapshot HTML with inlined resources
30
- scrape Extract CSS selectors (use --selectors "h1,.card")
31
- json AI-structured extraction (use --schema '{"title":"string"}')
32
- screenshot Capture PNG/JPEG (use --out ./page.png)
33
- pdf Capture PDF (use --out ./page.pdf)
34
-
35
- Options:
36
- --wait-until <value> load|domcontentloaded|networkidle0|networkidle2
37
- --selectors <csv> CSS selectors for scrape mode
38
- --schema <json> JSON schema object for json mode
39
- --out <path> Output path for screenshot/pdf
40
- --full-page fullPage screenshot (default true)
41
- --type <png|jpeg> screenshot type (default png)
42
- `);
43
- }
44
-
45
- function getOption(args, name) {
46
- const idx = args.indexOf(name);
47
- if (idx === -1 || idx + 1 >= args.length) return null;
48
- return args[idx + 1];
49
- }
50
-
51
- function hasFlag(args, name) {
52
- return args.includes(name);
53
- }
54
-
55
- async function verifyToken() {
56
- if (!API_TOKEN) {
57
- throw new Error("Missing API token. Set CLOUDFLARE_API_TOKEN or CF_TERRAFORM_TOKEN.");
58
- }
59
-
60
- const checks = [];
61
- if (ACCOUNT_ID) {
62
- checks.push({
63
- source: "account",
64
- url: `https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/tokens/verify`,
65
- });
66
- }
67
- checks.push({
68
- source: "user",
69
- url: "https://api.cloudflare.com/client/v4/user/tokens/verify",
70
- });
71
-
72
- const failures = [];
73
- for (const check of checks) {
74
- const res = await fetch(check.url, {
75
- headers: {
76
- Authorization: `Bearer ${API_TOKEN}`,
77
- "Content-Type": "application/json",
78
- },
79
- });
80
-
81
- const raw = await res.text();
82
- let data = null;
83
- try {
84
- data = raw ? JSON.parse(raw) : null;
85
- } catch {
86
- failures.push({
87
- source: check.source,
88
- status: res.status,
89
- message: `Non-JSON response: ${raw.slice(0, 200)}`,
90
- });
91
- continue;
92
- }
93
-
94
- if (res.ok && data?.success) {
95
- return {
96
- source: check.source,
97
- result: data.result,
98
- };
99
- }
100
-
101
- failures.push({
102
- source: check.source,
103
- status: res.status,
104
- message: JSON.stringify(data?.errors || data || raw),
105
- });
106
- }
107
-
108
- throw new Error(`Token verify failed: ${JSON.stringify(failures)}`);
109
- }
110
-
111
- async function callBrowserApi(mode, url, options) {
112
- if (!ACCOUNT_ID) {
113
- throw new Error("Missing CLOUDFLARE_ACCOUNT_ID.");
114
- }
115
- if (!API_TOKEN) {
116
- throw new Error("Missing API token. Set CLOUDFLARE_API_TOKEN or CF_TERRAFORM_TOKEN.");
117
- }
118
-
119
- const endpoint = ENDPOINTS.get(mode);
120
- if (!endpoint) {
121
- throw new Error(`Unsupported mode: ${mode}`);
122
- }
123
-
124
- const body = {
125
- url,
126
- };
127
-
128
- if (options.waitUntil) {
129
- body.waitUntil = options.waitUntil;
130
- }
131
- if (mode === "scrape" && options.selectors?.length) {
132
- body.selectors = options.selectors;
133
- }
134
- if (mode === "json" && options.schema) {
135
- body.schema = options.schema;
136
- }
137
- if (mode === "screenshot") {
138
- body.screenshotOptions = {
139
- type: options.type || "png",
140
- fullPage: options.fullPage,
141
- };
142
- }
143
- if (mode === "pdf") {
144
- body.pdfOptions = {
145
- printBackground: true,
146
- format: "A4",
147
- };
148
- }
149
-
150
- const res = await fetch(
151
- `https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/browser-rendering/${endpoint}`,
152
- {
153
- method: "POST",
154
- headers: {
155
- Authorization: `Bearer ${API_TOKEN}`,
156
- "Content-Type": "application/json",
157
- },
158
- body: JSON.stringify(body),
159
- }
160
- );
161
-
162
- if (!res.ok) {
163
- const text = await res.text().catch(() => "");
164
- throw new Error(`Browser Rendering API failed (${res.status}): ${text || res.statusText}`);
165
- }
166
-
167
- return res;
168
- }
169
-
170
- async function main() {
171
- const [, , mode, url, ...rest] = process.argv;
172
-
173
- if (!mode) {
174
- usage();
175
- process.exit(1);
176
- }
177
-
178
- if (mode === "verify") {
179
- const tokenInfo = await verifyToken();
180
- console.log(
181
- JSON.stringify(
182
- {
183
- status: "ok",
184
- verify_source: tokenInfo.source,
185
- token_status: tokenInfo.result?.status || "unknown",
186
- token_id: tokenInfo.result?.id || null,
187
- account_id_present: Boolean(ACCOUNT_ID),
188
- },
189
- null,
190
- 2
191
- )
192
- );
193
- return;
194
- }
195
-
196
- if (!url) {
197
- usage();
198
- process.exit(1);
199
- }
200
-
201
- const options = {
202
- waitUntil: getOption(rest, "--wait-until") || undefined,
203
- selectors: (getOption(rest, "--selectors") || "")
204
- .split(",")
205
- .map((v) => v.trim())
206
- .filter(Boolean),
207
- schema: getOption(rest, "--schema") ? JSON.parse(getOption(rest, "--schema")) : undefined,
208
- out: getOption(rest, "--out") || undefined,
209
- type: getOption(rest, "--type") || "png",
210
- fullPage: !hasFlag(rest, "--no-full-page"),
211
- };
212
-
213
- const res = await callBrowserApi(mode, url, options);
214
-
215
- if (mode === "screenshot" || mode === "pdf") {
216
- const outputPath = resolve(options.out || (mode === "pdf" ? "./cloudflare-page.pdf" : "./cloudflare-page.png"));
217
- const buffer = Buffer.from(await res.arrayBuffer());
218
- writeFileSync(outputPath, buffer);
219
- console.log(JSON.stringify({ status: "ok", mode, output: outputPath, bytes: buffer.length }, null, 2));
220
- return;
221
- }
222
-
223
- const contentType = res.headers.get("content-type") || "";
224
- if (contentType.includes("application/json")) {
225
- const json = await res.json();
226
- console.log(JSON.stringify(json, null, 2));
227
- return;
228
- }
229
-
230
- const text = await res.text();
231
- console.log(text);
232
- }
233
-
234
- main().catch((error) => {
235
- console.error(error instanceof Error ? error.message : String(error));
236
- process.exit(1);
237
- });
@@ -1,150 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { execSync } from "node:child_process";
4
- import { mkdirSync, writeFileSync } from "node:fs";
5
- import { join, resolve } from "node:path";
6
-
7
- const REPO =
8
- process.env.RELEASE_HEALTH_REPO ||
9
- process.env.GROWTH_REPO ||
10
- "jtalk22/slack-mcp-server";
11
- const NPM_PACKAGE =
12
- process.env.RELEASE_HEALTH_NPM_PACKAGE ||
13
- process.env.GROWTH_NPM_PACKAGE ||
14
- "@jtalk22/slack-mcp";
15
-
16
- function safeGhApi(path) {
17
- try {
18
- const out = execSync(`gh api ${path}`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
19
- return JSON.parse(out);
20
- } catch {
21
- return null;
22
- }
23
- }
24
-
25
- async function fetchJson(url) {
26
- const response = await fetch(url);
27
- if (!response.ok) {
28
- throw new Error(`Request failed: ${url} (${response.status})`);
29
- }
30
- return response.json();
31
- }
32
-
33
- function toDateSlug(date) {
34
- const y = date.getFullYear();
35
- const m = String(date.getMonth() + 1).padStart(2, "0");
36
- const d = String(date.getDate()).padStart(2, "0");
37
- return `${y}-${m}-${d}`;
38
- }
39
-
40
- function countNonPrIssues(items) {
41
- if (!Array.isArray(items)) return 0;
42
- return items.filter((item) => item && !item.pull_request).length;
43
- }
44
-
45
- function buildMarkdown(data) {
46
- const lines = [];
47
- lines.push("# Release Health Snapshot");
48
- lines.push("");
49
- lines.push(`- Generated: ${data.generatedAt}`);
50
- lines.push(`- Repo: \`${REPO}\``);
51
- lines.push(`- Package: \`${NPM_PACKAGE}\``);
52
- lines.push("");
53
-
54
- lines.push("## Install Signals");
55
- lines.push("");
56
- lines.push(`- npm downloads (last week): ${data.npm.lastWeek ?? "n/a"}`);
57
- lines.push(`- npm downloads (last month): ${data.npm.lastMonth ?? "n/a"}`);
58
- lines.push(`- npm latest version: ${data.npm.latestVersion ?? "n/a"}`);
59
- lines.push("");
60
-
61
- lines.push("## GitHub Reach");
62
- lines.push("");
63
- lines.push(`- stars: ${data.github.stars ?? "n/a"}`);
64
- lines.push(`- forks: ${data.github.forks ?? "n/a"}`);
65
- lines.push(`- open issues: ${data.github.openIssues ?? "n/a"}`);
66
- lines.push(`- 14d views: ${data.github.viewsCount ?? "n/a"}`);
67
- lines.push(`- 14d unique visitors: ${data.github.viewsUniques ?? "n/a"}`);
68
- lines.push(`- 14d clones: ${data.github.clonesCount ?? "n/a"}`);
69
- lines.push(`- 14d unique cloners: ${data.github.clonesUniques ?? "n/a"}`);
70
- lines.push(`- deployment-intake submissions (all-time): ${data.github.deploymentIntakeCount ?? "n/a"}`);
71
- lines.push("");
72
-
73
- lines.push("## 14-Day Reliability Targets (v3.0.0 Cycle)");
74
- lines.push("");
75
- lines.push("- weekly downloads: >= 180");
76
- lines.push("- qualified deployment-intake submissions: >= 2");
77
- lines.push("- maintainer support load: <= 2 hours/week");
78
- lines.push("");
79
-
80
- lines.push("## Notes");
81
- lines.push("");
82
- lines.push("- Update this snapshot daily during active release windows, then weekly.");
83
- lines.push("- Track deployment-intake quality and support load manually in issue notes.");
84
-
85
- return `${lines.join("\n")}\n`;
86
- }
87
-
88
- async function main() {
89
- const now = new Date();
90
- const generatedAt = now.toISOString();
91
- const dateSlug = toDateSlug(now);
92
-
93
- let npmWeek = null;
94
- let npmMonth = null;
95
- let npmMeta = null;
96
-
97
- try {
98
- npmWeek = await fetchJson(`https://api.npmjs.org/downloads/point/last-week/${encodeURIComponent(NPM_PACKAGE)}`);
99
- } catch {}
100
-
101
- try {
102
- npmMonth = await fetchJson(`https://api.npmjs.org/downloads/point/last-month/${encodeURIComponent(NPM_PACKAGE)}`);
103
- } catch {}
104
-
105
- try {
106
- npmMeta = await fetchJson(`https://registry.npmjs.org/${encodeURIComponent(NPM_PACKAGE)}`);
107
- } catch {}
108
-
109
- const repoInfo = safeGhApi(`repos/${REPO}`) || {};
110
- const views = safeGhApi(`repos/${REPO}/traffic/views`) || {};
111
- const clones = safeGhApi(`repos/${REPO}/traffic/clones`) || {};
112
- const intakeIssues = safeGhApi(`repos/${REPO}/issues?state=all&labels=deployment-intake&per_page=100`) || [];
113
-
114
- const data = {
115
- generatedAt,
116
- npm: {
117
- lastWeek: npmWeek?.downloads ?? null,
118
- lastMonth: npmMonth?.downloads ?? null,
119
- latestVersion: npmMeta?.["dist-tags"]?.latest ?? null,
120
- },
121
- github: {
122
- stars: repoInfo.stargazers_count ?? null,
123
- forks: repoInfo.forks_count ?? null,
124
- openIssues: repoInfo.open_issues_count ?? null,
125
- viewsCount: views.count ?? null,
126
- viewsUniques: views.uniques ?? null,
127
- clonesCount: clones.count ?? null,
128
- clonesUniques: clones.uniques ?? null,
129
- deploymentIntakeCount: countNonPrIssues(intakeIssues),
130
- },
131
- };
132
-
133
- const markdown = buildMarkdown(data);
134
-
135
- const metricsDir = resolve("docs", "release-health");
136
- const datedPath = join(metricsDir, `${dateSlug}.md`);
137
- const latestPath = join(metricsDir, "latest.md");
138
-
139
- mkdirSync(metricsDir, { recursive: true });
140
- writeFileSync(datedPath, markdown);
141
- writeFileSync(latestPath, markdown);
142
-
143
- console.log(`Wrote ${datedPath}`);
144
- console.log(`Wrote ${latestPath}`);
145
- }
146
-
147
- main().catch((error) => {
148
- console.error(error.message);
149
- process.exit(1);
150
- });
@@ -1,162 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Demo video recording script using Playwright
4
- * Records the Claude Desktop demo in fullscreen mode with auto-play
5
- *
6
- * Usage: npm run record-demo
7
- * Output: docs/videos/demo-claude-TIMESTAMP.webm
8
- */
9
-
10
- import { chromium } from 'playwright';
11
- import { fileURLToPath } from 'url';
12
- import { dirname, join } from 'path';
13
- import { mkdirSync, existsSync, copyFileSync } from 'fs';
14
-
15
- const __filename = fileURLToPath(import.meta.url);
16
- const __dirname = dirname(__filename);
17
- const projectRoot = join(__dirname, '..');
18
- const argv = process.argv.slice(2);
19
- const hasArg = (flag) => argv.includes(flag);
20
- const argValue = (flag) => {
21
- const idx = argv.indexOf(flag);
22
- return idx >= 0 && idx + 1 < argv.length ? argv[idx + 1] : null;
23
- };
24
-
25
- // Configuration
26
- const CONFIG = {
27
- viewport: { width: 1280, height: 800 },
28
- speed: '0.5', // Slow speed for video recording
29
- scenarioCount: 5,
30
- // Title and closing card durations (ms)
31
- introDuration: 4000, // Title card (3s visible + fade)
32
- outroDuration: 5500, // Closing card (4s visible + fades)
33
- // Approximate duration per scenario at 0.5x speed (in ms)
34
- scenarioDurations: {
35
- search: 26000, // +1s for transition fade
36
- thread: 26000,
37
- list: 21000,
38
- send: 19000,
39
- multi: 36000
40
- }
41
- };
42
-
43
- const canonicalOutput = argValue('--out') || join(projectRoot, 'docs', 'videos', 'demo-claude.webm');
44
- const archiveOutput = hasArg('--archive');
45
-
46
- async function recordDemo() {
47
- console.log('╔════════════════════════════════════════════════════════════╗');
48
- console.log('║ Slack MCP Server - Demo Video Recording ║');
49
- console.log('╚════════════════════════════════════════════════════════════╝');
50
- console.log();
51
-
52
- // Ensure videos directory exists
53
- const videosDir = join(projectRoot, 'docs', 'videos');
54
- if (!existsSync(videosDir)) {
55
- mkdirSync(videosDir, { recursive: true });
56
- console.log(`📁 Created directory: ${videosDir}`);
57
- }
58
-
59
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
60
- const timestampedOutput = join(videosDir, `demo-claude-${timestamp}.webm`);
61
-
62
- console.log('🚀 Launching browser...');
63
- const browser = await chromium.launch({
64
- headless: true,
65
- });
66
-
67
- const context = await browser.newContext({
68
- viewport: CONFIG.viewport,
69
- recordVideo: {
70
- dir: videosDir,
71
- size: CONFIG.viewport
72
- },
73
- colorScheme: 'dark'
74
- });
75
-
76
- const page = await context.newPage();
77
-
78
- // Load the demo
79
- const demoPath = join(projectRoot, 'public', 'demo-claude.html');
80
- console.log(`📄 Loading: ${demoPath}`);
81
- await page.goto(`file://${demoPath}`);
82
- await page.waitForTimeout(1000);
83
-
84
- // Hold on initial frame for a few seconds (visible first frame in GIF)
85
- console.log('⏸️ Holding initial frame (3s)...');
86
- await page.waitForTimeout(3000);
87
-
88
- // Set slow speed for video recording
89
- console.log(`⏱️ Setting speed to ${CONFIG.speed}x...`);
90
- await page.selectOption('#speedSelect', CONFIG.speed);
91
- await page.waitForTimeout(300);
92
-
93
- // Start auto-play BEFORE entering fullscreen (button hidden in fullscreen)
94
- console.log('▶️ Starting auto-play...');
95
- await page.click('#autoPlayBtn');
96
- await page.waitForTimeout(500);
97
-
98
- // Now enter fullscreen mode
99
- console.log('🖥️ Entering fullscreen mode...');
100
- console.log();
101
- await page.keyboard.press('f');
102
- await page.waitForTimeout(500);
103
-
104
- // Wait for title card
105
- console.log('📺 Showing title card...');
106
- await page.waitForTimeout(CONFIG.introDuration);
107
-
108
- // Wait for each scenario
109
- const scenarios = ['search', 'thread', 'list', 'send', 'multi'];
110
- let totalWait = 0;
111
-
112
- for (let i = 0; i < scenarios.length; i++) {
113
- const scenario = scenarios[i];
114
- const duration = CONFIG.scenarioDurations[scenario];
115
- totalWait += duration;
116
-
117
- console.log(` 📍 Scenario ${i + 1}/${scenarios.length}: ${scenario} (${Math.round(duration/1000)}s)`);
118
- await page.waitForTimeout(duration);
119
- }
120
-
121
- // Wait for closing card
122
- console.log();
123
- console.log('🎬 Showing closing screen...');
124
- await page.waitForTimeout(CONFIG.outroDuration);
125
-
126
- // Exit fullscreen
127
- console.log('🔚 Exiting fullscreen...');
128
- await page.keyboard.press('Escape');
129
- await page.waitForTimeout(500);
130
-
131
- // Close context to flush video
132
- console.log('💾 Saving video...');
133
- const video = await page.video();
134
- await context.close();
135
- await browser.close();
136
-
137
- // Get the actual video path
138
- const videoPath = await video.path();
139
-
140
- console.log();
141
- console.log('╔════════════════════════════════════════════════════════════╗');
142
- console.log('║ ✅ Recording Complete! ║');
143
- console.log('╚════════════════════════════════════════════════════════════╝');
144
- console.log();
145
- copyFileSync(videoPath, canonicalOutput);
146
- console.log(`📹 Canonical video: ${canonicalOutput}`);
147
- if (archiveOutput) {
148
- copyFileSync(videoPath, timestampedOutput);
149
- console.log(`🗂️ Archived copy: ${timestampedOutput}`);
150
- }
151
- console.log();
152
- console.log('Next steps:');
153
- console.log(' 1. Review the canonical video in a media player');
154
- console.log(' 2. Convert to GIF with FFmpeg:');
155
- console.log(` ffmpeg -i "${canonicalOutput}" -vf "fps=15,scale=800:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" docs/images/demo-claude.gif`);
156
- console.log(' 3. Re-run with --archive to keep timestamped historical outputs');
157
- }
158
-
159
- recordDemo().catch(err => {
160
- console.error('❌ Recording failed:', err.message);
161
- process.exit(1);
162
- });