@jtalk22/slack-mcp 2.0.0 → 3.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.
- package/README.md +112 -64
- package/docs/CLOUDFLARE-BROWSER-TOOLKIT.md +67 -0
- package/docs/DEPLOYMENT-MODES.md +10 -3
- package/docs/HN-LAUNCH.md +47 -36
- package/docs/INDEX.md +4 -1
- package/docs/INSTALL-PROOF.md +5 -5
- package/docs/LAUNCH-COPY-v3.0.0.md +73 -0
- package/docs/LAUNCH-MATRIX.md +4 -2
- package/docs/LAUNCH-OPS.md +24 -23
- package/docs/RELEASE-HEALTH.md +9 -0
- package/docs/TROUBLESHOOTING.md +27 -0
- package/docs/WEB-API.md +13 -4
- package/docs/images/demo-channel-messages.png +0 -0
- package/docs/images/demo-channels.png +0 -0
- package/docs/images/demo-claude-mobile-360x800.png +0 -0
- package/docs/images/demo-claude-mobile-390x844.png +0 -0
- package/docs/images/demo-main-mobile-360x800.png +0 -0
- package/docs/images/demo-main-mobile-390x844.png +0 -0
- package/docs/images/demo-main.png +0 -0
- package/docs/images/demo-poster.png +0 -0
- package/docs/images/demo-sidebar.png +0 -0
- package/docs/images/web-api-mobile-360x800.png +0 -0
- package/docs/images/web-api-mobile-390x844.png +0 -0
- package/package.json +14 -6
- package/public/demo-claude.html +83 -10
- package/public/demo-video.html +33 -4
- package/public/demo.html +136 -2
- package/public/index.html +132 -69
- package/scripts/capture-screenshots.js +103 -53
- package/scripts/check-version-parity.js +25 -11
- package/scripts/cloudflare-browser-tool.js +237 -0
- package/scripts/collect-release-health.js +1 -1
- package/scripts/record-demo.js +22 -9
- package/scripts/release-preflight.js +243 -0
- package/scripts/setup-wizard.js +1 -1
- package/scripts/verify-install-flow.js +2 -1
- package/scripts/verify-web.js +49 -1
- package/server.json +47 -0
- package/smithery.yaml +34 -0
- package/src/server-http.js +98 -5
- package/src/server.js +18 -6
- package/src/web-server.js +5 -3
- package/docs/LAUNCH-COPY-v2.0.0.md +0 -59
- package/docs/images/demo-claude-v1.2.gif +0 -0
- package/docs/images/demo-readme.gif +0 -0
- package/docs/release-health/2026-02-25.md +0 -33
- package/docs/release-health/2026-02-26.md +0 -33
- package/docs/release-health/24h-delta.md +0 -21
- package/docs/release-health/24h-end.md +0 -33
- package/docs/release-health/24h-start.md +0 -33
- package/docs/release-health/latest.md +0 -33
- package/docs/release-health/launch-log-template.md +0 -21
- package/docs/release-health/version-parity.md +0 -21
- package/docs/videos/.gitkeep +0 -0
- package/docs/videos/demo-claude-v1.2.webm +0 -0
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Screenshot capture script using Playwright
|
|
4
|
-
* Captures
|
|
4
|
+
* Captures desktop + mobile screenshots for README/docs
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { chromium } from 'playwright';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { dirname, join } from 'path';
|
|
10
|
-
import { readFileSync } from 'fs';
|
|
11
10
|
|
|
12
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
12
|
const __dirname = dirname(__filename);
|
|
14
13
|
const projectRoot = join(__dirname, '..');
|
|
14
|
+
const imagesDir = join(projectRoot, 'docs', 'images');
|
|
15
|
+
|
|
16
|
+
const viewports = [
|
|
17
|
+
{ width: 390, height: 844, suffix: '390x844' },
|
|
18
|
+
{ width: 360, height: 800, suffix: '360x800' }
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
async function openPage(browser, filePath, viewport) {
|
|
22
|
+
const context = await browser.newContext({
|
|
23
|
+
viewport,
|
|
24
|
+
deviceScaleFactor: 2,
|
|
25
|
+
colorScheme: 'dark'
|
|
26
|
+
});
|
|
27
|
+
const page = await context.newPage();
|
|
28
|
+
await page.goto(`file://${filePath}`);
|
|
29
|
+
await page.waitForTimeout(1000);
|
|
30
|
+
return { context, page };
|
|
31
|
+
}
|
|
15
32
|
|
|
16
33
|
async function captureScreenshots() {
|
|
17
34
|
console.log('Launching browser...');
|
|
@@ -20,68 +37,94 @@ async function captureScreenshots() {
|
|
|
20
37
|
headless: true
|
|
21
38
|
});
|
|
22
39
|
|
|
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
40
|
const demoPath = join(projectRoot, 'public', 'demo.html');
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
// Serve it as a data URL or file URL
|
|
36
|
-
await page.goto(`file://${demoPath}`);
|
|
41
|
+
const demoClaudePath = join(projectRoot, 'public', 'demo-claude.html');
|
|
42
|
+
const indexPath = join(projectRoot, 'public', 'index.html');
|
|
37
43
|
|
|
38
|
-
//
|
|
39
|
-
|
|
44
|
+
// Desktop captures from demo.html
|
|
45
|
+
{
|
|
46
|
+
const { context, page } = await openPage(browser, demoPath, { width: 1400, height: 900 });
|
|
40
47
|
|
|
41
|
-
|
|
48
|
+
console.log('Capturing desktop demo screenshots...');
|
|
49
|
+
await page.screenshot({
|
|
50
|
+
path: join(imagesDir, 'demo-main.png'),
|
|
51
|
+
clip: { x: 0, y: 0, width: 1400, height: 800 }
|
|
52
|
+
});
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
const mainPanel = await page.$('.main-panel');
|
|
55
|
+
if (mainPanel) {
|
|
56
|
+
await mainPanel.screenshot({
|
|
57
|
+
path: join(imagesDir, 'demo-messages.png')
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const sidebar = await page.$('.sidebar');
|
|
62
|
+
if (sidebar) {
|
|
63
|
+
await sidebar.screenshot({
|
|
64
|
+
path: join(imagesDir, 'demo-sidebar.png')
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await page.evaluate(() => runScenario('listChannels'));
|
|
69
|
+
await page.waitForTimeout(2600);
|
|
70
|
+
await page.screenshot({
|
|
71
|
+
path: join(imagesDir, 'demo-channels.png'),
|
|
72
|
+
clip: { x: 0, y: 0, width: 1400, height: 800 }
|
|
73
|
+
});
|
|
49
74
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
path: join(imagesDir, 'demo-messages.png')
|
|
75
|
+
await page.click('.conversation-item:first-child');
|
|
76
|
+
await page.waitForTimeout(600);
|
|
77
|
+
await page.screenshot({
|
|
78
|
+
path: join(imagesDir, 'demo-channel-messages.png'),
|
|
79
|
+
clip: { x: 0, y: 0, width: 1400, height: 800 }
|
|
56
80
|
});
|
|
81
|
+
|
|
82
|
+
await context.close();
|
|
57
83
|
}
|
|
58
84
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
await
|
|
64
|
-
path: join(imagesDir, 'demo-
|
|
85
|
+
// Poster from the Claude demo
|
|
86
|
+
{
|
|
87
|
+
const { context, page } = await openPage(browser, demoClaudePath, { width: 1280, height: 800 });
|
|
88
|
+
console.log('Capturing poster image...');
|
|
89
|
+
await page.screenshot({
|
|
90
|
+
path: join(imagesDir, 'demo-poster.png'),
|
|
91
|
+
clip: { x: 0, y: 0, width: 1280, height: 800 }
|
|
65
92
|
});
|
|
93
|
+
await context.close();
|
|
66
94
|
}
|
|
67
95
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
// Mobile captures for web, demo, and claude demo pages
|
|
97
|
+
for (const viewport of viewports) {
|
|
98
|
+
const label = `${viewport.width}x${viewport.height}`;
|
|
99
|
+
console.log(`Capturing mobile screenshots (${label})...`);
|
|
100
|
+
|
|
101
|
+
{
|
|
102
|
+
const { context, page } = await openPage(browser, demoPath, viewport);
|
|
103
|
+
await page.screenshot({
|
|
104
|
+
path: join(imagesDir, `demo-main-mobile-${viewport.suffix}.png`),
|
|
105
|
+
fullPage: true
|
|
106
|
+
});
|
|
107
|
+
await context.close();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
{
|
|
111
|
+
const { context, page } = await openPage(browser, demoClaudePath, viewport);
|
|
112
|
+
await page.screenshot({
|
|
113
|
+
path: join(imagesDir, `demo-claude-mobile-${viewport.suffix}.png`),
|
|
114
|
+
fullPage: true
|
|
115
|
+
});
|
|
116
|
+
await context.close();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
{
|
|
120
|
+
const { context, page } = await openPage(browser, indexPath, viewport);
|
|
121
|
+
await page.screenshot({
|
|
122
|
+
path: join(imagesDir, `web-api-mobile-${viewport.suffix}.png`),
|
|
123
|
+
fullPage: true
|
|
124
|
+
});
|
|
125
|
+
await context.close();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
85
128
|
|
|
86
129
|
await browser.close();
|
|
87
130
|
|
|
@@ -91,6 +134,13 @@ async function captureScreenshots() {
|
|
|
91
134
|
console.log(' - demo-sidebar.png');
|
|
92
135
|
console.log(' - demo-channels.png');
|
|
93
136
|
console.log(' - demo-channel-messages.png');
|
|
137
|
+
console.log(' - demo-poster.png');
|
|
138
|
+
console.log(' - demo-main-mobile-390x844.png');
|
|
139
|
+
console.log(' - demo-main-mobile-360x800.png');
|
|
140
|
+
console.log(' - demo-claude-mobile-390x844.png');
|
|
141
|
+
console.log(' - demo-claude-mobile-360x800.png');
|
|
142
|
+
console.log(' - web-api-mobile-390x844.png');
|
|
143
|
+
console.log(' - web-api-mobile-360x800.png');
|
|
94
144
|
}
|
|
95
145
|
|
|
96
146
|
captureScreenshots().catch(console.error);
|
|
@@ -17,6 +17,7 @@ const allowPropagation = process.argv.includes("--allow-propagation");
|
|
|
17
17
|
|
|
18
18
|
const mcpServerName = serverMeta.name;
|
|
19
19
|
const smitheryEndpoint = "https://server.smithery.ai/jtalk22/slack-mcp-server";
|
|
20
|
+
const smitheryListingUrl = "https://smithery.ai/server/jtalk22/slack-mcp-server";
|
|
20
21
|
|
|
21
22
|
async function fetchJson(url) {
|
|
22
23
|
const res = await fetch(url);
|
|
@@ -26,12 +27,10 @@ async function fetchJson(url) {
|
|
|
26
27
|
return res.json();
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
async function
|
|
30
|
+
async function fetchStatus(url) {
|
|
30
31
|
const res = await fetch(url);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
return res.text();
|
|
32
|
+
const text = await res.text().catch(() => "");
|
|
33
|
+
return { ok: res.ok, status: res.status, text };
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
function row(surface, version, status, note = "") {
|
|
@@ -46,6 +45,7 @@ async function main() {
|
|
|
46
45
|
let npmVersion = null;
|
|
47
46
|
let mcpRegistryVersion = null;
|
|
48
47
|
let smitheryReachable = null;
|
|
48
|
+
let smitheryStatus = null;
|
|
49
49
|
let npmError = null;
|
|
50
50
|
let mcpError = null;
|
|
51
51
|
let smitheryError = null;
|
|
@@ -67,8 +67,18 @@ async function main() {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
try {
|
|
70
|
-
const
|
|
71
|
-
|
|
70
|
+
const apiResult = await fetchStatus(smitheryEndpoint);
|
|
71
|
+
smitheryStatus = apiResult.status;
|
|
72
|
+
if (apiResult.ok) {
|
|
73
|
+
smitheryReachable = true;
|
|
74
|
+
} else if (apiResult.status === 401 || apiResult.status === 403) {
|
|
75
|
+
// Auth-gated endpoint still indicates the listing endpoint is live.
|
|
76
|
+
smitheryReachable = true;
|
|
77
|
+
} else {
|
|
78
|
+
const listingResult = await fetchStatus(smitheryListingUrl);
|
|
79
|
+
smitheryStatus = `${apiResult.status} (api), ${listingResult.status} (listing)`;
|
|
80
|
+
smitheryReachable = listingResult.ok && listingResult.text.length > 0;
|
|
81
|
+
}
|
|
72
82
|
} catch (error) {
|
|
73
83
|
smitheryError = String(error?.message || error);
|
|
74
84
|
}
|
|
@@ -127,7 +137,9 @@ async function main() {
|
|
|
127
137
|
"Smithery endpoint",
|
|
128
138
|
"n/a",
|
|
129
139
|
smitheryReachable ? "reachable" : "unreachable",
|
|
130
|
-
smitheryError
|
|
140
|
+
smitheryError
|
|
141
|
+
? `check_error: ${smitheryError}`
|
|
142
|
+
: `status: ${smitheryStatus ?? "unknown"}; version check is manual.`
|
|
131
143
|
),
|
|
132
144
|
"",
|
|
133
145
|
"## Interpretation",
|
|
@@ -138,9 +150,11 @@ async function main() {
|
|
|
138
150
|
externalMismatches.length === 0
|
|
139
151
|
? "- External parity: pass."
|
|
140
152
|
: `- External parity mismatch: ${externalMismatches.map((f) => f.name).join(", ")}.`,
|
|
141
|
-
|
|
142
|
-
? "- Propagation mode
|
|
143
|
-
:
|
|
153
|
+
externalMismatches.length === 0
|
|
154
|
+
? "- Propagation mode: not needed (external parity is already aligned)."
|
|
155
|
+
: (allowPropagation
|
|
156
|
+
? "- Propagation mode enabled: external mismatch accepted temporarily."
|
|
157
|
+
: "- Propagation mode disabled: external mismatch is a release gate failure."),
|
|
144
158
|
];
|
|
145
159
|
|
|
146
160
|
const outPath = join(repoRoot, outputArg);
|
|
@@ -0,0 +1,237 @@
|
|
|
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
|
+
});
|
|
@@ -70,7 +70,7 @@ function buildMarkdown(data) {
|
|
|
70
70
|
lines.push(`- deployment-intake submissions (all-time): ${data.github.deploymentIntakeCount ?? "n/a"}`);
|
|
71
71
|
lines.push("");
|
|
72
72
|
|
|
73
|
-
lines.push("## 14-Day Reliability Targets (
|
|
73
|
+
lines.push("## 14-Day Reliability Targets (v3.0.0 Cycle)");
|
|
74
74
|
lines.push("");
|
|
75
75
|
lines.push("- weekly downloads: >= 180");
|
|
76
76
|
lines.push("- qualified deployment-intake submissions: >= 2");
|
package/scripts/record-demo.js
CHANGED
|
@@ -10,11 +10,17 @@
|
|
|
10
10
|
import { chromium } from 'playwright';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
12
|
import { dirname, join } from 'path';
|
|
13
|
-
import { mkdirSync, existsSync } from 'fs';
|
|
13
|
+
import { mkdirSync, existsSync, copyFileSync } from 'fs';
|
|
14
14
|
|
|
15
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
16
|
const __dirname = dirname(__filename);
|
|
17
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
|
+
};
|
|
18
24
|
|
|
19
25
|
// Configuration
|
|
20
26
|
const CONFIG = {
|
|
@@ -34,6 +40,9 @@ const CONFIG = {
|
|
|
34
40
|
}
|
|
35
41
|
};
|
|
36
42
|
|
|
43
|
+
const canonicalOutput = argValue('--out') || join(projectRoot, 'docs', 'videos', 'demo-claude.webm');
|
|
44
|
+
const archiveOutput = hasArg('--archive');
|
|
45
|
+
|
|
37
46
|
async function recordDemo() {
|
|
38
47
|
console.log('╔════════════════════════════════════════════════════════════╗');
|
|
39
48
|
console.log('║ Slack MCP Server - Demo Video Recording ║');
|
|
@@ -47,13 +56,12 @@ async function recordDemo() {
|
|
|
47
56
|
console.log(`📁 Created directory: ${videosDir}`);
|
|
48
57
|
}
|
|
49
58
|
|
|
50
|
-
// Generate timestamped filename
|
|
51
59
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
52
|
-
const
|
|
60
|
+
const timestampedOutput = join(videosDir, `demo-claude-${timestamp}.webm`);
|
|
53
61
|
|
|
54
62
|
console.log('🚀 Launching browser...');
|
|
55
63
|
const browser = await chromium.launch({
|
|
56
|
-
headless:
|
|
64
|
+
headless: true,
|
|
57
65
|
});
|
|
58
66
|
|
|
59
67
|
const context = await browser.newContext({
|
|
@@ -134,13 +142,18 @@ async function recordDemo() {
|
|
|
134
142
|
console.log('║ ✅ Recording Complete! ║');
|
|
135
143
|
console.log('╚════════════════════════════════════════════════════════════╝');
|
|
136
144
|
console.log();
|
|
137
|
-
|
|
145
|
+
copyFileSync(videoPath, canonicalOutput);
|
|
146
|
+
console.log(`📹 Canonical video: ${canonicalOutput}`);
|
|
147
|
+
if (archiveOutput) {
|
|
148
|
+
copyFileSync(videoPath, timestampedOutput);
|
|
149
|
+
console.log(`🗂️ Archived copy: ${timestampedOutput}`);
|
|
150
|
+
}
|
|
138
151
|
console.log();
|
|
139
152
|
console.log('Next steps:');
|
|
140
|
-
console.log(' 1. Review the video in a media player');
|
|
141
|
-
console.log(' 2. Convert to GIF
|
|
142
|
-
console.log(
|
|
143
|
-
console.log(
|
|
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');
|
|
144
157
|
}
|
|
145
158
|
|
|
146
159
|
recordDemo().catch(err => {
|