@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,1958 +0,0 @@
1
- <!--
2
- Slack MCP Server - Claude Desktop Demo
3
- Author: @jtalk22
4
- Repository: https://github.com/jtalk22/slack-mcp-server
5
- License: MIT
6
- Created: January 2026
7
- -->
8
- <!DOCTYPE html>
9
- <html lang="en">
10
- <head>
11
- <meta charset="UTF-8">
12
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
13
- <meta name="author" content="@jtalk22">
14
- <title>Slack MCP Server - Claude Desktop Demo</title>
15
- <meta name="description" content="Session-based Slack access for Claude with your existing workspace permissions. Demo workflows for DMs, channels, search, and threads.">
16
-
17
- <!-- Open Graph -->
18
- <meta property="og:title" content="Slack MCP Server - Session-Based Slack Access Demo">
19
- <meta property="og:description" content="Session-based Slack access for Claude with your existing workspace permissions. Search, thread, and messaging workflows.">
20
- <meta property="og:type" content="website">
21
- <meta property="og:url" content="https://jtalk22.github.io/slack-mcp-server/public/demo-claude.html">
22
- <meta property="og:image" content="https://raw.githubusercontent.com/jtalk22/slack-mcp-server/main/docs/images/demo-claude.gif">
23
-
24
- <!-- Twitter Card -->
25
- <meta name="twitter:card" content="summary_large_image">
26
- <meta name="twitter:title" content="Slack MCP Server - Session-Based Slack Access Demo">
27
- <meta name="twitter:description" content="Session-based Slack access for Claude with your existing workspace permissions. Search, thread, and messaging workflows.">
28
- <meta name="twitter:image" content="https://raw.githubusercontent.com/jtalk22/slack-mcp-server/main/docs/images/demo-claude.gif">
29
-
30
- <!-- Theme -->
31
- <meta name="theme-color" content="#1a1a1a">
32
- <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>💬</text></svg>">
33
- <style>
34
- @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=Space+Grotesk:wght@500;600;700&display=swap');
35
-
36
- /* ═══════════════════════════════════════════════════════════════
37
- Claude Desktop Color Palette (Dark Mode)
38
- ═══════════════════════════════════════════════════════════════ */
39
- :root {
40
- --font-heading: "Space Grotesk", "Avenir Next", "Segoe UI", sans-serif;
41
- --font-body: "IBM Plex Sans", "Inter", "Segoe UI", sans-serif;
42
-
43
- /* Window chrome */
44
- --window-bg: #1a1a1a;
45
- --window-chrome: #2d2d2d;
46
- --window-border: #3a3a3a;
47
- --traffic-red: #ff5f57;
48
- --traffic-yellow: #febc2e;
49
- --traffic-green: #28c840;
50
-
51
- /* Messages */
52
- --user-bubble-bg: #3b3b3b;
53
- --claude-bubble-bg: #2a2a2a;
54
- --claude-orange: #da7756;
55
-
56
- /* Tool calls */
57
- --tool-box-bg: #1f1f1f;
58
- --tool-box-border: #3a3a3a;
59
- --tool-header-bg: #252525;
60
- --tool-name-color: #a0a0a0;
61
-
62
- /* Text */
63
- --text-primary: #ffffff;
64
- --text-secondary: #b0b0b0;
65
- --text-muted: #666666;
66
-
67
- /* Accents */
68
- --link-color: #6eb5ff;
69
- --code-bg: #2d2d2d;
70
- --code-text: #e6e6e6;
71
- --success-color: #28c840;
72
- --warning-color: #febc2e;
73
-
74
- /* Brand DNA (subliminal) */
75
- --text-warm: #E8E4DF;
76
-
77
- /* Shadows */
78
- --shadow-lg: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
79
- }
80
-
81
- /* ═══════════════════════════════════════════════════════════════
82
- Typography (SF Pro / System)
83
- ═══════════════════════════════════════════════════════════════ */
84
- * {
85
- margin: 0;
86
- padding: 0;
87
- box-sizing: border-box;
88
- }
89
-
90
- body {
91
- font-family: var(--font-body);
92
- font-size: 15px;
93
- line-height: 1.5;
94
- -webkit-font-smoothing: antialiased;
95
- -moz-osx-font-smoothing: grayscale;
96
- background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
97
- min-height: 100vh;
98
- display: flex;
99
- flex-direction: column;
100
- align-items: center;
101
- justify-content: center;
102
- padding: 20px;
103
- color: var(--text-primary);
104
- }
105
-
106
- .mono {
107
- font-family: "SF Mono", "Menlo", "Monaco", monospace;
108
- }
109
-
110
- /* ═══════════════════════════════════════════════════════════════
111
- Page Header
112
- ═══════════════════════════════════════════════════════════════ */
113
- .page-header {
114
- text-align: center;
115
- margin-bottom: 24px;
116
- }
117
-
118
- .page-header h1 {
119
- font-size: 28px;
120
- font-weight: 600;
121
- margin-bottom: 8px;
122
- display: flex;
123
- align-items: center;
124
- justify-content: center;
125
- gap: 12px;
126
- font-family: var(--font-heading);
127
- letter-spacing: -0.02em;
128
- }
129
-
130
- .page-header p {
131
- color: var(--text-secondary);
132
- font-size: 16px;
133
- }
134
- .cta-strip {
135
- width: 100%;
136
- max-width: 960px;
137
- margin-bottom: 14px;
138
- background: rgba(15, 52, 96, 0.72);
139
- border: 1px solid rgba(255, 255, 255, 0.15);
140
- border-radius: 12px;
141
- padding: 10px 12px;
142
- display: flex;
143
- justify-content: space-between;
144
- align-items: center;
145
- gap: 10px;
146
- flex-wrap: wrap;
147
- font-size: 13px;
148
- }
149
- .cta-strip .links {
150
- display: flex;
151
- gap: 8px;
152
- flex-wrap: wrap;
153
- }
154
- .cta-strip .links a {
155
- color: #d8efff;
156
- text-decoration: none;
157
- border: 1px solid rgba(255, 255, 255, 0.25);
158
- border-radius: 999px;
159
- padding: 4px 8px;
160
- }
161
- .cta-strip .links a:hover {
162
- background: rgba(255, 255, 255, 0.08);
163
- }
164
- .cta-strip .note {
165
- color: rgba(255, 255, 255, 0.78);
166
- }
167
- .cta-strip .note a {
168
- color: #9ee7ff;
169
- text-decoration: underline;
170
- }
171
-
172
- .badge {
173
- display: inline-flex;
174
- align-items: center;
175
- gap: 6px;
176
- background: rgba(218, 119, 86, 0.2);
177
- color: var(--claude-orange);
178
- padding: 4px 10px;
179
- border-radius: 12px;
180
- font-size: 12px;
181
- font-weight: 500;
182
- }
183
-
184
- /* ═══════════════════════════════════════════════════════════════
185
- Scenario Selector
186
- ═══════════════════════════════════════════════════════════════ */
187
- .scenario-bar {
188
- display: flex;
189
- gap: 12px;
190
- margin-bottom: 24px;
191
- flex-wrap: wrap;
192
- justify-content: center;
193
- width: min(100%, 960px);
194
- }
195
-
196
- .scenario-btn {
197
- display: flex;
198
- align-items: center;
199
- gap: 8px;
200
- padding: 12px 20px;
201
- background: rgba(255, 255, 255, 0.05);
202
- border: 1px solid rgba(255, 255, 255, 0.1);
203
- border-radius: 12px;
204
- color: var(--text-secondary);
205
- cursor: pointer;
206
- transition: all 0.2s ease;
207
- font-size: 14px;
208
- font-weight: 500;
209
- }
210
-
211
- .scenario-btn:hover {
212
- background: rgba(255, 255, 255, 0.1);
213
- border-color: rgba(255, 255, 255, 0.2);
214
- color: var(--text-primary);
215
- transform: translateY(-2px);
216
- }
217
-
218
- .scenario-btn.active {
219
- background: rgba(218, 119, 86, 0.2);
220
- border-color: var(--claude-orange);
221
- color: var(--claude-orange);
222
- }
223
-
224
- .scenario-btn.playing {
225
- animation: pulse-border 1.5s ease-in-out infinite;
226
- }
227
-
228
- @keyframes pulse-border {
229
- 0%, 100% { box-shadow: 0 0 0 0 rgba(218, 119, 86, 0.4); }
230
- 50% { box-shadow: 0 0 0 4px rgba(218, 119, 86, 0); }
231
- }
232
-
233
- .scenario-btn .icon {
234
- font-size: 18px;
235
- }
236
-
237
- .replay-btn {
238
- display: flex;
239
- align-items: center;
240
- gap: 6px;
241
- padding: 10px 16px;
242
- background: rgba(110, 181, 255, 0.1);
243
- border: 1px solid rgba(110, 181, 255, 0.3);
244
- border-radius: 12px;
245
- color: var(--link-color);
246
- cursor: pointer;
247
- transition: all 0.2s ease;
248
- font-size: 13px;
249
- font-weight: 500;
250
- }
251
-
252
- .replay-btn:hover {
253
- background: rgba(110, 181, 255, 0.2);
254
- transform: translateY(-2px);
255
- }
256
-
257
- .replay-btn:disabled {
258
- opacity: 0.5;
259
- cursor: not-allowed;
260
- transform: none;
261
- }
262
-
263
- .controls-bar {
264
- display: flex;
265
- gap: 12px;
266
- margin-bottom: 16px;
267
- align-items: center;
268
- flex-wrap: wrap;
269
- justify-content: center;
270
- width: min(100%, 920px);
271
- }
272
-
273
- .speed-control {
274
- display: flex;
275
- align-items: center;
276
- gap: 8px;
277
- color: var(--text-muted);
278
- font-size: 12px;
279
- }
280
-
281
- .speed-control select {
282
- background: rgba(255, 255, 255, 0.05);
283
- border: 1px solid rgba(255, 255, 255, 0.1);
284
- border-radius: 6px;
285
- color: var(--text-secondary);
286
- padding: 6px 10px;
287
- font-size: 12px;
288
- cursor: pointer;
289
- }
290
-
291
- .speed-control select:focus {
292
- outline: none;
293
- border-color: var(--link-color);
294
- }
295
-
296
- .progress-indicator {
297
- display: flex;
298
- align-items: center;
299
- gap: 8px;
300
- color: var(--text-muted);
301
- font-size: 12px;
302
- }
303
-
304
- .progress-bar {
305
- width: 80px;
306
- height: 4px;
307
- background: rgba(255, 255, 255, 0.1);
308
- border-radius: 2px;
309
- overflow: hidden;
310
- }
311
-
312
- .progress-fill {
313
- height: 100%;
314
- background: var(--success-color);
315
- border-radius: 2px;
316
- transition: width 0.3s ease;
317
- }
318
-
319
- .share-btn {
320
- display: flex;
321
- align-items: center;
322
- gap: 6px;
323
- padding: 8px 14px;
324
- background: rgba(255, 255, 255, 0.05);
325
- border: 1px solid rgba(255, 255, 255, 0.1);
326
- border-radius: 8px;
327
- color: var(--text-secondary);
328
- cursor: pointer;
329
- transition: all 0.2s ease;
330
- font-size: 12px;
331
- font-weight: 500;
332
- }
333
-
334
- .share-btn:hover {
335
- background: rgba(255, 255, 255, 0.1);
336
- color: var(--text-primary);
337
- }
338
-
339
- .share-btn.copied {
340
- background: rgba(40, 200, 64, 0.15);
341
- border-color: var(--success-color);
342
- color: var(--success-color);
343
- }
344
-
345
- .share-btn .share-icon {
346
- font-size: 14px;
347
- }
348
-
349
- /* ═══════════════════════════════════════════════════════════════
350
- Claude Desktop Window Frame
351
- ═══════════════════════════════════════════════════════════════ */
352
- .claude-window {
353
- width: 100%;
354
- max-width: 800px;
355
- background: var(--window-bg);
356
- border-radius: 12px;
357
- box-shadow: var(--shadow-lg);
358
- overflow: hidden;
359
- border: 1px solid var(--window-border);
360
- }
361
-
362
- .window-chrome {
363
- height: 52px;
364
- background: var(--window-chrome);
365
- display: flex;
366
- align-items: center;
367
- padding: 0 16px;
368
- border-bottom: 1px solid var(--window-border);
369
- }
370
-
371
- .traffic-lights {
372
- display: flex;
373
- gap: 8px;
374
- }
375
-
376
- .traffic-light {
377
- width: 12px;
378
- height: 12px;
379
- border-radius: 50%;
380
- }
381
-
382
- .traffic-light.red { background: var(--traffic-red); }
383
- .traffic-light.yellow { background: var(--traffic-yellow); }
384
- .traffic-light.green { background: var(--traffic-green); }
385
-
386
- .window-title {
387
- flex: 1;
388
- text-align: center;
389
- font-size: 13px;
390
- color: var(--text-secondary);
391
- font-weight: 500;
392
- }
393
-
394
- .window-controls {
395
- width: 52px;
396
- }
397
-
398
- /* ═══════════════════════════════════════════════════════════════
399
- Chat Container
400
- ═══════════════════════════════════════════════════════════════ */
401
- .chat-container {
402
- height: 520px;
403
- overflow-y: auto;
404
- padding: 24px;
405
- display: flex;
406
- flex-direction: column;
407
- gap: 20px;
408
- scroll-behavior: smooth;
409
- }
410
-
411
- /* Loading Skeleton */
412
- .loading-skeleton {
413
- display: flex;
414
- flex-direction: column;
415
- gap: 16px;
416
- }
417
-
418
- .skeleton-message {
419
- display: flex;
420
- gap: 12px;
421
- align-items: flex-start;
422
- }
423
-
424
- .skeleton-avatar {
425
- width: 28px;
426
- height: 28px;
427
- border-radius: 50%;
428
- background: linear-gradient(90deg, var(--window-border) 25%, #4a4a4a 50%, var(--window-border) 75%);
429
- background-size: 200% 100%;
430
- animation: skeleton-shimmer 1.5s infinite;
431
- }
432
-
433
- .skeleton-lines {
434
- flex: 1;
435
- display: flex;
436
- flex-direction: column;
437
- gap: 8px;
438
- }
439
-
440
- .skeleton-line {
441
- height: 16px;
442
- border-radius: 4px;
443
- background: linear-gradient(90deg, var(--window-border) 25%, #4a4a4a 50%, var(--window-border) 75%);
444
- background-size: 200% 100%;
445
- animation: skeleton-shimmer 1.5s infinite;
446
- }
447
-
448
- @keyframes skeleton-shimmer {
449
- 0% { background-position: 200% 0; }
450
- 100% { background-position: -200% 0; }
451
- }
452
-
453
- .chat-container::-webkit-scrollbar {
454
- width: 8px;
455
- }
456
-
457
- .chat-container::-webkit-scrollbar-track {
458
- background: transparent;
459
- }
460
-
461
- .chat-container::-webkit-scrollbar-thumb {
462
- background: var(--window-border);
463
- border-radius: 4px;
464
- }
465
-
466
- /* ═══════════════════════════════════════════════════════════════
467
- Message Bubbles
468
- ═══════════════════════════════════════════════════════════════ */
469
- .message {
470
- max-width: 100%;
471
- animation: message-appear 0.3s ease-out;
472
- }
473
-
474
- @keyframes message-appear {
475
- from {
476
- opacity: 0;
477
- transform: translateY(10px);
478
- }
479
- to {
480
- opacity: 1;
481
- transform: translateY(0);
482
- }
483
- }
484
-
485
- .message-header {
486
- display: flex;
487
- align-items: center;
488
- gap: 10px;
489
- margin-bottom: 8px;
490
- }
491
-
492
- .message-avatar {
493
- width: 28px;
494
- height: 28px;
495
- border-radius: 50%;
496
- display: flex;
497
- align-items: center;
498
- justify-content: center;
499
- font-size: 14px;
500
- }
501
-
502
- .message.user .message-avatar {
503
- background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
504
- color: white;
505
- font-weight: 600;
506
- font-size: 12px;
507
- }
508
-
509
- .message.claude .message-avatar {
510
- background: linear-gradient(135deg, var(--claude-orange) 0%, #c56644 100%);
511
- color: white;
512
- font-weight: 600;
513
- font-size: 12px;
514
- }
515
-
516
- .message.claude .message-avatar svg {
517
- width: 16px;
518
- height: 16px;
519
- }
520
-
521
- .message-sender {
522
- font-weight: 600;
523
- font-size: 14px;
524
- }
525
-
526
- .message.user .message-sender { color: #a5b4fc; }
527
- .message.claude .message-sender { color: var(--claude-orange); }
528
-
529
- .message-time {
530
- color: var(--text-muted);
531
- font-size: 12px;
532
- }
533
-
534
- .message-content {
535
- padding: 16px;
536
- border-radius: 12px;
537
- font-size: 15px;
538
- line-height: 1.6;
539
- }
540
-
541
- .message.user .message-content {
542
- background: var(--user-bubble-bg);
543
- }
544
-
545
- .message.claude .message-content {
546
- background: var(--claude-bubble-bg);
547
- }
548
-
549
- /* ═══════════════════════════════════════════════════════════════
550
- Tool Call Box
551
- ═══════════════════════════════════════════════════════════════ */
552
- .tool-call {
553
- background: var(--tool-box-bg);
554
- border: 1px solid var(--tool-box-border);
555
- border-radius: 8px;
556
- margin: 12px 0;
557
- overflow: hidden;
558
- }
559
-
560
- .tool-header {
561
- display: flex;
562
- align-items: center;
563
- gap: 10px;
564
- padding: 12px 16px;
565
- background: var(--tool-header-bg);
566
- cursor: pointer;
567
- transition: background 0.2s;
568
- }
569
-
570
- .tool-header:hover {
571
- background: #2a2a2a;
572
- }
573
-
574
- .tool-icon {
575
- font-size: 16px;
576
- }
577
-
578
- .tool-name {
579
- font-family: "SF Mono", monospace;
580
- font-size: 13px;
581
- color: var(--link-color);
582
- font-weight: 500;
583
- }
584
-
585
- .tool-status {
586
- margin-left: auto;
587
- display: inline-flex;
588
- align-items: center;
589
- gap: 6px;
590
- font-size: 12px;
591
- color: var(--text-muted);
592
- }
593
-
594
- .tool-status.running {
595
- color: var(--warning-color);
596
- }
597
-
598
- .tool-status.running::before {
599
- content: '';
600
- display: inline-block;
601
- width: 12px;
602
- height: 12px;
603
- border: 2px solid var(--warning-color);
604
- border-top-color: transparent;
605
- border-radius: 50%;
606
- animation: spin 0.8s linear infinite;
607
- margin-right: 6px;
608
- }
609
-
610
- @keyframes spin {
611
- to { transform: rotate(360deg); }
612
- }
613
-
614
- .tool-status.success {
615
- color: var(--success-color);
616
- }
617
-
618
- .tool-chevron {
619
- color: var(--text-muted);
620
- transition: transform 0.3s ease;
621
- font-size: 10px;
622
- }
623
-
624
- .tool-call.expanded .tool-chevron {
625
- transform: rotate(180deg);
626
- }
627
-
628
- .tool-body {
629
- max-height: 0;
630
- overflow: hidden;
631
- transition: max-height 0.3s ease-out;
632
- }
633
-
634
- .tool-call.expanded .tool-body {
635
- max-height: 400px;
636
- }
637
-
638
- .tool-section {
639
- padding: 12px 16px;
640
- border-top: 1px solid var(--tool-box-border);
641
- }
642
-
643
- .tool-section-label {
644
- font-size: 11px;
645
- text-transform: uppercase;
646
- letter-spacing: 0.5px;
647
- color: var(--text-muted);
648
- margin-bottom: 8px;
649
- }
650
-
651
- .tool-params {
652
- font-family: "SF Mono", monospace;
653
- font-size: 13px;
654
- color: var(--code-text);
655
- background: var(--code-bg);
656
- padding: 12px;
657
- border-radius: 6px;
658
- white-space: pre-wrap;
659
- }
660
-
661
- .tool-result {
662
- font-size: 13px;
663
- color: var(--text-secondary);
664
- }
665
-
666
- .tool-result .result-item {
667
- padding: 8px 12px;
668
- margin: 0 -12px;
669
- border-bottom: 1px solid var(--tool-box-border);
670
- border-left: 2px solid transparent;
671
- border-radius: 4px;
672
- transition: all 0.15s ease;
673
- cursor: default;
674
- }
675
-
676
- .tool-result .result-item:hover {
677
- background: rgba(255, 255, 255, 0.03);
678
- border-left-color: var(--claude-orange);
679
- }
680
-
681
- .tool-result .result-item:last-child {
682
- border-bottom: none;
683
- }
684
-
685
- .result-channel {
686
- color: var(--link-color);
687
- font-weight: 500;
688
- }
689
-
690
- .result-user {
691
- color: #f0abfc;
692
- font-weight: 500;
693
- }
694
-
695
- .result-time {
696
- color: var(--text-muted);
697
- font-size: 12px;
698
- }
699
-
700
- /* ═══════════════════════════════════════════════════════════════
701
- Typing Indicator
702
- ═══════════════════════════════════════════════════════════════ */
703
- .typing-indicator {
704
- display: flex;
705
- align-items: center;
706
- gap: 8px;
707
- padding: 12px 16px;
708
- background: var(--claude-bubble-bg);
709
- border-radius: 12px;
710
- width: fit-content;
711
- }
712
-
713
- .typing-dots {
714
- display: flex;
715
- gap: 4px;
716
- }
717
-
718
- .typing-dot {
719
- width: 8px;
720
- height: 8px;
721
- background: var(--text-muted);
722
- border-radius: 50%;
723
- animation: typing-bounce 1.4s ease-in-out infinite;
724
- }
725
-
726
- .typing-dot:nth-child(2) { animation-delay: 0.16s; }
727
- .typing-dot:nth-child(3) { animation-delay: 0.32s; }
728
-
729
- @keyframes typing-bounce {
730
- 0%, 80%, 100% { transform: translateY(0); opacity: 0.4; }
731
- 40% { transform: translateY(-4px); opacity: 1; }
732
- }
733
-
734
- .typing-cursor {
735
- display: inline-block;
736
- width: 2px;
737
- height: 1em;
738
- background: var(--claude-orange);
739
- margin-left: 1px;
740
- animation: cursor-blink 0.8s ease-in-out infinite;
741
- vertical-align: text-bottom;
742
- }
743
-
744
- @keyframes cursor-blink {
745
- 0%, 50% { opacity: 1; }
746
- 51%, 100% { opacity: 0; }
747
- }
748
-
749
- /* ═══════════════════════════════════════════════════════════════
750
- Input Bar
751
- ═══════════════════════════════════════════════════════════════ */
752
- .input-bar {
753
- display: flex;
754
- align-items: center;
755
- padding: 16px 20px;
756
- background: var(--window-chrome);
757
- border-top: 1px solid var(--window-border);
758
- gap: 12px;
759
- }
760
-
761
- .input-field {
762
- flex: 1;
763
- background: var(--window-bg);
764
- border: 1px solid var(--window-border);
765
- border-radius: 24px;
766
- padding: 12px 20px;
767
- color: var(--text-secondary);
768
- font-size: 14px;
769
- }
770
-
771
- .tools-button {
772
- display: flex;
773
- align-items: center;
774
- gap: 6px;
775
- padding: 10px 16px;
776
- background: rgba(218, 119, 86, 0.15);
777
- border: 1px solid rgba(218, 119, 86, 0.3);
778
- border-radius: 20px;
779
- color: var(--claude-orange);
780
- cursor: pointer;
781
- transition: all 0.2s;
782
- font-size: 13px;
783
- font-weight: 500;
784
- position: relative;
785
- }
786
-
787
- .tools-button:hover {
788
- background: rgba(218, 119, 86, 0.25);
789
- }
790
-
791
- .tools-button .icon {
792
- font-size: 16px;
793
- }
794
-
795
- /* ═══════════════════════════════════════════════════════════════
796
- Tools Dropdown
797
- ═══════════════════════════════════════════════════════════════ */
798
- .tools-dropdown {
799
- position: absolute;
800
- bottom: calc(100% + 8px);
801
- right: 0;
802
- width: 320px;
803
- background: var(--window-bg);
804
- border: 1px solid var(--window-border);
805
- border-radius: 12px;
806
- box-shadow: var(--shadow-lg);
807
- opacity: 0;
808
- visibility: hidden;
809
- transform: translateY(10px);
810
- transition: all 0.2s ease;
811
- z-index: 100;
812
- }
813
-
814
- .tools-button:hover .tools-dropdown,
815
- .tools-dropdown:hover {
816
- opacity: 1;
817
- visibility: visible;
818
- transform: translateY(0);
819
- }
820
-
821
- .dropdown-header {
822
- padding: 12px 16px;
823
- border-bottom: 1px solid var(--window-border);
824
- font-weight: 600;
825
- font-size: 14px;
826
- display: flex;
827
- align-items: center;
828
- gap: 8px;
829
- }
830
-
831
- .dropdown-list {
832
- max-height: 300px;
833
- overflow-y: auto;
834
- }
835
-
836
- .dropdown-item {
837
- padding: 10px 16px;
838
- border-bottom: 1px solid var(--tool-box-border);
839
- cursor: default;
840
- }
841
-
842
- .dropdown-item:last-child {
843
- border-bottom: none;
844
- }
845
-
846
- .dropdown-item:hover {
847
- background: rgba(255, 255, 255, 0.03);
848
- }
849
-
850
- .dropdown-item-name {
851
- font-family: "SF Mono", monospace;
852
- font-size: 13px;
853
- color: var(--link-color);
854
- margin-bottom: 2px;
855
- }
856
-
857
- .dropdown-item-desc {
858
- font-size: 12px;
859
- color: var(--text-muted);
860
- }
861
-
862
- /* ═══════════════════════════════════════════════════════════════
863
- Code Inline
864
- ═══════════════════════════════════════════════════════════════ */
865
- code {
866
- font-family: "SF Mono", monospace;
867
- background: var(--code-bg);
868
- padding: 2px 6px;
869
- border-radius: 4px;
870
- font-size: 13px;
871
- }
872
-
873
- strong {
874
- font-weight: 600;
875
- }
876
-
877
- em {
878
- font-style: italic;
879
- color: var(--text-secondary);
880
- }
881
-
882
- /* ═══════════════════════════════════════════════════════════════
883
- Footer
884
- ═══════════════════════════════════════════════════════════════ */
885
- .page-footer {
886
- margin-top: 24px;
887
- text-align: center;
888
- color: var(--text-muted);
889
- font-size: 13px;
890
- }
891
-
892
- .page-footer a {
893
- color: var(--link-color);
894
- text-decoration: none;
895
- }
896
-
897
- .page-footer a:hover {
898
- text-decoration: underline;
899
- }
900
-
901
- kbd {
902
- display: inline-block;
903
- padding: 2px 6px;
904
- font-family: "SF Mono", monospace;
905
- font-size: 10px;
906
- background: rgba(255, 255, 255, 0.1);
907
- border: 1px solid rgba(255, 255, 255, 0.2);
908
- border-radius: 4px;
909
- color: var(--text-secondary);
910
- }
911
-
912
- /* Fullscreen / Presentation Mode */
913
- body.fullscreen-mode {
914
- padding: 0;
915
- justify-content: center;
916
- }
917
-
918
- body.fullscreen-mode .page-header,
919
- body.fullscreen-mode .scenario-bar,
920
- body.fullscreen-mode .controls-bar,
921
- body.fullscreen-mode .page-footer {
922
- display: none !important;
923
- }
924
-
925
- body.fullscreen-mode .claude-window {
926
- max-width: 100%;
927
- height: 100vh;
928
- border-radius: 0;
929
- border: none;
930
- }
931
-
932
- body.fullscreen-mode .chat-container {
933
- height: calc(100vh - 52px - 68px);
934
- }
935
-
936
- .fullscreen-hint {
937
- position: fixed;
938
- bottom: 20px;
939
- right: 20px;
940
- background: rgba(0, 0, 0, 0.8);
941
- color: var(--text-secondary);
942
- padding: 8px 12px;
943
- border-radius: 6px;
944
- font-size: 12px;
945
- opacity: 0;
946
- transition: opacity 0.3s;
947
- pointer-events: none;
948
- }
949
-
950
- body.fullscreen-mode .fullscreen-hint {
951
- opacity: 1;
952
- }
953
-
954
- /* ═══════════════════════════════════════════════════════════════
955
- Accessibility
956
- ═══════════════════════════════════════════════════════════════ */
957
- @media (prefers-reduced-motion: reduce) {
958
- *, *::before, *::after {
959
- animation-duration: 0.01ms !important;
960
- animation-iteration-count: 1 !important;
961
- transition-duration: 0.01ms !important;
962
- }
963
- }
964
-
965
- .scenario-btn:focus-visible,
966
- .replay-btn:focus-visible,
967
- .tools-button:focus-visible,
968
- .speed-control select:focus-visible {
969
- outline: 2px solid var(--link-color);
970
- outline-offset: 2px;
971
- }
972
-
973
- .tool-header:focus-visible {
974
- outline: 2px solid var(--link-color);
975
- outline-offset: -2px;
976
- }
977
-
978
- /* ═══════════════════════════════════════════════════════════════
979
- Responsive
980
- ═══════════════════════════════════════════════════════════════ */
981
- @media (max-width: 900px) {
982
- body {
983
- padding: 14px;
984
- }
985
-
986
- .page-header {
987
- margin-bottom: 16px;
988
- }
989
-
990
- .page-header h1 {
991
- font-size: 24px;
992
- }
993
-
994
- .scenario-btn {
995
- padding: 10px 14px;
996
- }
997
-
998
- .controls-bar {
999
- gap: 10px;
1000
- }
1001
-
1002
- .claude-window {
1003
- max-width: 100%;
1004
- }
1005
- }
1006
-
1007
- @media (max-width: 600px) {
1008
- body {
1009
- padding: 12px;
1010
- }
1011
-
1012
- .cta-strip {
1013
- padding: 10px;
1014
- gap: 8px;
1015
- }
1016
-
1017
- .cta-strip .links {
1018
- width: 100%;
1019
- }
1020
-
1021
- .cta-strip .note {
1022
- font-size: 12px;
1023
- }
1024
-
1025
- .page-header h1 {
1026
- font-size: 22px;
1027
- flex-wrap: wrap;
1028
- gap: 8px;
1029
- }
1030
-
1031
- .page-header p {
1032
- font-size: 14px;
1033
- }
1034
-
1035
- .scenario-bar {
1036
- gap: 8px;
1037
- margin-bottom: 14px;
1038
- }
1039
-
1040
- .scenario-btn {
1041
- padding: 9px 12px;
1042
- font-size: 13px;
1043
- border-radius: 10px;
1044
- }
1045
-
1046
- .scenario-btn .label {
1047
- display: none;
1048
- }
1049
-
1050
- .controls-bar {
1051
- gap: 8px;
1052
- margin-bottom: 12px;
1053
- }
1054
-
1055
- .replay-btn,
1056
- .share-btn {
1057
- padding: 8px 12px;
1058
- }
1059
-
1060
- .speed-control {
1061
- width: 100%;
1062
- justify-content: space-between;
1063
- }
1064
-
1065
- .chat-container {
1066
- height: min(55vh, 450px);
1067
- padding: 14px;
1068
- }
1069
-
1070
- .tools-dropdown {
1071
- width: 280px;
1072
- right: auto;
1073
- left: 50%;
1074
- transform: translateX(-50%) translateY(0);
1075
- }
1076
-
1077
- .tools-button:hover .tools-dropdown,
1078
- .tools-dropdown:hover {
1079
- transform: translateX(-50%) translateY(0);
1080
- }
1081
- }
1082
-
1083
- /* ═══════════════════════════════════════════════════════════════
1084
- Production Polish - Title, Captions, Transitions, Closing
1085
- ═══════════════════════════════════════════════════════════════ */
1086
-
1087
- /* Title Card */
1088
- .title-card {
1089
- position: absolute;
1090
- inset: 0;
1091
- top: 32px; /* Below window chrome */
1092
- display: none;
1093
- flex-direction: column;
1094
- align-items: center;
1095
- justify-content: center;
1096
- background: var(--window-bg);
1097
- z-index: 100;
1098
- opacity: 0;
1099
- transition: opacity 0.5s ease;
1100
- }
1101
-
1102
- .title-card.visible {
1103
- display: flex;
1104
- opacity: 1;
1105
- }
1106
-
1107
- .title-card h1 {
1108
- color: var(--text-warm);
1109
- letter-spacing: 0.08em;
1110
- font-weight: 300;
1111
- font-size: 28px;
1112
- margin: 16px 0 8px;
1113
- }
1114
-
1115
- .title-card .title-logo { font-size: 48px; }
1116
- .title-card .title-tagline { color: var(--text-secondary); font-size: 16px; }
1117
- .title-card .title-version { color: var(--text-muted); font-size: 13px; margin-top: 24px; }
1118
-
1119
- /* Scenario Caption Overlay */
1120
- .scenario-caption {
1121
- position: absolute;
1122
- top: 72px;
1123
- left: 50%;
1124
- transform: translateX(-50%);
1125
- background: rgba(0, 0, 0, 0.75);
1126
- color: var(--text-primary);
1127
- padding: 8px 20px;
1128
- border-radius: 20px;
1129
- font-size: 13px;
1130
- font-weight: 500;
1131
- opacity: 0;
1132
- transition: opacity 0.3s ease;
1133
- z-index: 50;
1134
- pointer-events: none;
1135
- }
1136
-
1137
- .scenario-caption.visible {
1138
- opacity: 1;
1139
- }
1140
-
1141
- /* Smooth Transitions */
1142
- .chat-container {
1143
- transition: opacity 0.3s ease;
1144
- }
1145
-
1146
- .chat-container.fading {
1147
- opacity: 0;
1148
- }
1149
-
1150
- /* Closing Card */
1151
- .closing-card {
1152
- position: absolute;
1153
- inset: 0;
1154
- top: 32px;
1155
- display: none;
1156
- flex-direction: column;
1157
- align-items: center;
1158
- justify-content: center;
1159
- background: var(--window-bg);
1160
- z-index: 100;
1161
- opacity: 0;
1162
- transition: opacity 0.5s ease;
1163
- }
1164
-
1165
- .closing-card.visible {
1166
- display: flex;
1167
- opacity: 1;
1168
- }
1169
-
1170
- .closing-check { font-size: 48px; margin-bottom: 16px; }
1171
-
1172
- .closing-card h2 {
1173
- color: var(--text-warm);
1174
- font-weight: 400;
1175
- font-size: 24px;
1176
- margin-bottom: 8px;
1177
- }
1178
-
1179
- .closing-cta {
1180
- color: var(--text-secondary);
1181
- margin: 8px 0 24px;
1182
- font-size: 15px;
1183
- }
1184
-
1185
- .closing-links code {
1186
- background: var(--code-bg);
1187
- color: var(--code-text);
1188
- padding: 12px 24px;
1189
- border-radius: 8px;
1190
- font-size: 14px;
1191
- font-family: "SF Mono", "Menlo", monospace;
1192
- }
1193
-
1194
- .closing-github {
1195
- margin-top: 24px;
1196
- color: var(--link-color);
1197
- font-size: 14px;
1198
- }
1199
-
1200
- /* ê Easter Egg - The Rêvasser Wink */
1201
- .easter-egg {
1202
- position: absolute;
1203
- bottom: 16px;
1204
- right: 20px;
1205
- color: var(--text-warm);
1206
- opacity: 0.15;
1207
- font-size: 14px;
1208
- font-weight: 300;
1209
- font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;
1210
- }
1211
-
1212
- .closing-card.visible .easter-egg {
1213
- animation: egg-wink 8s ease 2s forwards;
1214
- }
1215
-
1216
- @keyframes egg-wink {
1217
- 0%, 100% { opacity: 0.15; }
1218
- 10% { opacity: 0.35; }
1219
- 20% { opacity: 0.15; }
1220
- }
1221
- </style>
1222
- </head>
1223
- <body>
1224
- <div class="cta-strip">
1225
- <div class="links">
1226
- <a href="https://www.npmjs.com/package/@jtalk22/slack-mcp" target="_blank" rel="noopener noreferrer">Install</a>
1227
- <a href="https://github.com/jtalk22/slack-mcp-server/blob/main/docs/SETUP.md" target="_blank" rel="noopener noreferrer">Setup Guide</a>
1228
- <a href="https://github.com/jtalk22/slack-mcp-server#30-second-compatibility-check" target="_blank" rel="noopener noreferrer">30-Second Check</a>
1229
- </div>
1230
- <div class="note">
1231
- Free local-first path with team rollout references in <a href="https://github.com/jtalk22/slack-mcp-server/blob/main/docs/DEPLOYMENT-MODES.md" target="_blank" rel="noopener noreferrer">Deployment Modes</a>.
1232
- </div>
1233
- </div>
1234
- <header class="page-header">
1235
- <h1>
1236
- <span>Slack MCP Server</span>
1237
- <span class="badge">🔧 MCP Demo v3.0.0</span>
1238
- </h1>
1239
- <p>See how Claude uses MCP tools to access your Slack workspace</p>
1240
- </header>
1241
-
1242
- <div class="scenario-bar" role="tablist" aria-label="Demo scenarios">
1243
- <button class="scenario-btn active" data-scenario="search" onclick="runScenario('search')" role="tab" aria-selected="true" aria-label="Search DMs scenario">
1244
- <span class="icon" aria-hidden="true">🔍</span>
1245
- <span class="label">Search DMs</span>
1246
- </button>
1247
- <button class="scenario-btn" data-scenario="thread" onclick="runScenario('thread')" role="tab" aria-selected="false" aria-label="Get Thread scenario">
1248
- <span class="icon" aria-hidden="true">📜</span>
1249
- <span class="label">Get Thread</span>
1250
- </button>
1251
- <button class="scenario-btn" data-scenario="list" onclick="runScenario('list')" role="tab" aria-selected="false" aria-label="List DMs scenario">
1252
- <span class="icon" aria-hidden="true">💬</span>
1253
- <span class="label">List DMs</span>
1254
- </button>
1255
- <button class="scenario-btn" data-scenario="send" onclick="runScenario('send')" role="tab" aria-selected="false" aria-label="Send Message scenario">
1256
- <span class="icon" aria-hidden="true">✉️</span>
1257
- <span class="label">Send Message</span>
1258
- </button>
1259
- <button class="scenario-btn" data-scenario="multi" onclick="runScenario('multi')" role="tab" aria-selected="false" aria-label="Multi-Tool scenario">
1260
- <span class="icon" aria-hidden="true">⚡</span>
1261
- <span class="label">Multi-Tool</span>
1262
- </button>
1263
- </div>
1264
-
1265
- <div class="controls-bar" role="toolbar" aria-label="Demo controls">
1266
- <button class="replay-btn" id="replayBtn" onclick="replayScenario()" aria-label="Replay current scenario">
1267
- <span aria-hidden="true">↻</span>
1268
- <span>Replay</span>
1269
- </button>
1270
- <button class="replay-btn" id="autoPlayBtn" onclick="autoPlayAll()" style="background: rgba(40, 200, 64, 0.1); border-color: rgba(40, 200, 64, 0.3); color: var(--success-color);" aria-label="Auto-play all scenarios">
1271
- <span aria-hidden="true">▶</span>
1272
- <span>Auto-Play All</span>
1273
- </button>
1274
- <div class="speed-control">
1275
- <label>Speed:</label>
1276
- <select id="speedSelect" onchange="updateSpeed(this.value)">
1277
- <option value="0.5">0.5x (Slow - Video)</option>
1278
- <option value="1" selected>1x (Normal)</option>
1279
- <option value="1.5">1.5x (Fast)</option>
1280
- <option value="2">2x (Fastest)</option>
1281
- </select>
1282
- </div>
1283
- <div class="progress-indicator" id="progressIndicator" style="display: none;">
1284
- <span class="progress-text"></span>
1285
- <div class="progress-bar"><div class="progress-fill"></div></div>
1286
- </div>
1287
- <button class="share-btn" onclick="copyShareLink()" aria-label="Copy link to share">
1288
- <span class="share-icon">🔗</span>
1289
- <span class="share-text">Share</span>
1290
- </button>
1291
- </div>
1292
-
1293
- <div class="claude-window">
1294
- <div class="window-chrome">
1295
- <div class="traffic-lights">
1296
- <div class="traffic-light red"></div>
1297
- <div class="traffic-light yellow"></div>
1298
- <div class="traffic-light green"></div>
1299
- </div>
1300
- <div class="window-title">Claude</div>
1301
- <div class="window-controls"></div>
1302
- </div>
1303
-
1304
- <!-- Title Card (auto-play only) -->
1305
- <div class="title-card" id="titleCard">
1306
- <div class="title-logo">💬</div>
1307
- <h1>Slack MCP Server</h1>
1308
- <p class="title-tagline">Full Slack access for Claude Desktop</p>
1309
- <p class="title-version">v3.0.0 • @jtalk22</p>
1310
- </div>
1311
-
1312
- <!-- Scenario Caption Overlay -->
1313
- <div class="scenario-caption" id="scenarioCaption"></div>
1314
-
1315
- <!-- Closing Card (auto-play only) -->
1316
- <div class="closing-card" id="closingCard">
1317
- <div class="closing-check">✅</div>
1318
- <h2>Demo Complete</h2>
1319
- <p class="closing-cta">Session-based Slack access aligned to your existing workspace permissions.</p>
1320
- <div class="closing-links">
1321
- <code>npx -y @jtalk22/slack-mcp --setup</code>
1322
- </div>
1323
- <p class="closing-github">github.com/jtalk22/slack-mcp-server</p>
1324
- <span class="easter-egg">ê</span>
1325
- </div>
1326
-
1327
- <div class="chat-container" id="chatContainer" role="log" aria-label="Chat demonstration" aria-live="polite">
1328
- <!-- Loading skeleton shown before first scenario -->
1329
- <div class="loading-skeleton" id="loadingSkeleton">
1330
- <div class="skeleton-message">
1331
- <div class="skeleton-avatar"></div>
1332
- <div class="skeleton-lines">
1333
- <div class="skeleton-line" style="width: 60%"></div>
1334
- <div class="skeleton-line" style="width: 80%"></div>
1335
- </div>
1336
- </div>
1337
- </div>
1338
- </div>
1339
-
1340
- <div class="input-bar">
1341
- <div class="input-field">Message Claude...</div>
1342
- <div class="tools-button">
1343
- <span class="icon">🔨</span>
1344
- <span>11 tools</span>
1345
- <div class="tools-dropdown">
1346
- <div class="dropdown-header">
1347
- <span>🔨</span> Available Slack Tools
1348
- </div>
1349
- <div class="dropdown-list">
1350
- <div class="dropdown-item">
1351
- <div class="dropdown-item-name">slack_search_messages</div>
1352
- <div class="dropdown-item-desc">Search across your workspace</div>
1353
- </div>
1354
- <div class="dropdown-item">
1355
- <div class="dropdown-item-name">slack_list_conversations</div>
1356
- <div class="dropdown-item-desc">List DMs and channels</div>
1357
- </div>
1358
- <div class="dropdown-item">
1359
- <div class="dropdown-item-name">slack_conversations_history</div>
1360
- <div class="dropdown-item-desc">Get messages from a conversation</div>
1361
- </div>
1362
- <div class="dropdown-item">
1363
- <div class="dropdown-item-name">slack_get_thread</div>
1364
- <div class="dropdown-item-desc">Get all replies in a thread</div>
1365
- </div>
1366
- <div class="dropdown-item">
1367
- <div class="dropdown-item-name">slack_send_message</div>
1368
- <div class="dropdown-item-desc">Send a message to any channel</div>
1369
- </div>
1370
- <div class="dropdown-item">
1371
- <div class="dropdown-item-name">slack_get_full_conversation</div>
1372
- <div class="dropdown-item-desc">Export full history with threads</div>
1373
- </div>
1374
- <div class="dropdown-item">
1375
- <div class="dropdown-item-name">slack_users_info</div>
1376
- <div class="dropdown-item-desc">Get user details</div>
1377
- </div>
1378
- <div class="dropdown-item">
1379
- <div class="dropdown-item-name">slack_list_users</div>
1380
- <div class="dropdown-item-desc">List workspace users</div>
1381
- </div>
1382
- <div class="dropdown-item">
1383
- <div class="dropdown-item-name">slack_health_check</div>
1384
- <div class="dropdown-item-desc">Verify token validity</div>
1385
- </div>
1386
- <div class="dropdown-item">
1387
- <div class="dropdown-item-name">slack_token_status</div>
1388
- <div class="dropdown-item-desc">Check token health</div>
1389
- </div>
1390
- <div class="dropdown-item">
1391
- <div class="dropdown-item-name">slack_refresh_tokens</div>
1392
- <div class="dropdown-item-desc">Auto-extract fresh tokens</div>
1393
- </div>
1394
- </div>
1395
- </div>
1396
- </div>
1397
- </div>
1398
- </div>
1399
-
1400
- <div class="fullscreen-hint">Press <kbd>F</kbd> or <kbd>Esc</kbd> to exit</div>
1401
-
1402
- <footer class="page-footer">
1403
- <p>
1404
- Made by <a href="https://github.com/jtalk22" target="_blank">@jtalk22</a> ·
1405
- <a href="https://github.com/jtalk22/slack-mcp-server" target="_blank">GitHub</a> ·
1406
- <a href="https://www.npmjs.com/package/@jtalk22/slack-mcp" target="_blank">npm</a> ·
1407
- <a href="demo.html">Web UI Demo</a>
1408
- </p>
1409
- <p style="margin-top: 8px; font-size: 11px; color: var(--text-muted);">
1410
- Keyboard: <kbd>1-5</kbd> scenarios · <kbd>R</kbd> replay · <kbd>A</kbd> auto-play · <kbd>F</kbd> fullscreen · <kbd>Esc</kbd> exit
1411
- </p>
1412
- <p style="margin-top: 12px; font-size: 11px; color: var(--text-muted);">
1413
- © 2026 James Lambert · MIT License
1414
- </p>
1415
- </footer>
1416
-
1417
- <script>
1418
- // ═══════════════════════════════════════════════════════════════
1419
- // Scenario Data
1420
- // ═══════════════════════════════════════════════════════════════
1421
- const scenarios = {
1422
- search: {
1423
- userMessage: "Find all messages about the API key from last week",
1424
- claudeIntro: "I'll search your Slack workspace for messages about API keys from the past week.",
1425
- toolCall: {
1426
- name: "slack_search_messages",
1427
- params: {
1428
- query: "API key",
1429
- count: 20
1430
- },
1431
- result: [
1432
- { channel: "#engineering", user: "Sarah Chen", time: "Jan 15, 2:34 PM", text: "Here's the staging API key: <code>sk-abc123-staging-xyz</code>" },
1433
- { channel: "DM with Mike", user: "Mike Johnson", time: "Jan 12, 10:15 AM", text: "Don't forget to rotate the API key before the deploy" },
1434
- { channel: "#alerts", user: "DevOps Bot", time: "Jan 10, 9:00 AM", text: "API key rotation reminder: 3 days until expiry" }
1435
- ]
1436
- },
1437
- claudeSummary: "I found <strong>3 messages</strong> mentioning API keys from last week:\n\n1. <strong>Sarah Chen</strong> shared the staging API key in #engineering on Jan 15th\n2. <strong>Mike Johnson</strong> reminded you about key rotation in a DM on Jan 12th\n3. <strong>DevOps Bot</strong> posted an expiry reminder in #alerts on Jan 10th\n\nWould you like me to get more details on any of these?"
1438
- },
1439
-
1440
- thread: {
1441
- userMessage: "What was decided in the Q4 launch thread?",
1442
- claudeIntro: "Let me find that thread and get the full context of the discussion.",
1443
- toolCall: {
1444
- name: "slack_get_thread",
1445
- params: {
1446
- channel: "C04N8JXKZ7M",
1447
- thread_ts: "1704729600.847293"
1448
- },
1449
- result: [
1450
- { user: "Alex Kim", time: "Jan 8, 3:00 PM", text: "Kicking off Q4 launch planning. Key dates?" },
1451
- { user: "Sarah Chen", time: "Jan 8, 3:15 PM", text: "Marketing wants the 15th for press embargo lift" },
1452
- { user: "Mike Johnson", time: "Jan 8, 3:22 PM", text: "Engineering can hit the 12th for feature freeze" },
1453
- { user: "Jordan Lee", time: "Jan 8, 4:00 PM", text: "Let's lock in: Feature freeze Jan 12, Launch Jan 18" },
1454
- { user: "Alex Kim", time: "Jan 8, 4:05 PM", text: "✅ Approved. I'll update the roadmap" }
1455
- ]
1456
- },
1457
- claudeSummary: "The Q4 launch thread concluded with these <strong>key decisions</strong>:\n\n• <strong>Feature freeze:</strong> January 12th\n• <strong>Press embargo lift:</strong> January 15th \n• <strong>Launch date:</strong> January 18th\n\n<strong>Jordan Lee</strong> proposed the final timeline and <strong>Alex Kim</strong> approved it. Would you like me to pull any related threads or documents?"
1458
- },
1459
-
1460
- list: {
1461
- userMessage: "Who has messaged me recently?",
1462
- claudeIntro: "I'll check your recent direct messages to see who's reached out.",
1463
- toolCall: {
1464
- name: "slack_list_conversations",
1465
- params: {
1466
- types: "im",
1467
- limit: 10,
1468
- discover_dms: true
1469
- },
1470
- result: [
1471
- { name: "Sarah Chen", id: "D04AXQK8N2P", lastMessage: "2 hours ago", preview: "Thanks for the review!" },
1472
- { name: "Mike Johnson", id: "D04BYR7M3Q1", lastMessage: "Yesterday", preview: "Can we sync on the API changes?" },
1473
- { name: "Alex Kim", id: "D04CZS6L4R2", lastMessage: "2 days ago", preview: "Q4 roadmap is updated" },
1474
- { name: "Jordan Lee", id: "D04DWT5K5S3", lastMessage: "3 days ago", preview: "Meeting notes attached" },
1475
- { name: "DevOps Bot", id: "D04EXU4J6T4", lastMessage: "4 days ago", preview: "Deploy completed successfully" }
1476
- ]
1477
- },
1478
- claudeSummary: "You have recent DMs from <strong>5 people</strong>:\n\n• <strong>Sarah Chen</strong> — 2 hours ago: \"Thanks for the review!\"\n• <strong>Mike Johnson</strong> — Yesterday: Asking about API changes sync\n• <strong>Alex Kim</strong> — 2 days ago: Q4 roadmap update\n• <strong>Jordan Lee</strong> — 3 days ago: Meeting notes\n• <strong>DevOps Bot</strong> — 4 days ago: Deploy notification\n\nWant me to pull the full conversation with any of them?"
1479
- },
1480
-
1481
- send: {
1482
- userMessage: "Send a quick update to #general that the deploy is complete",
1483
- claudeIntro: "I'll post that update to #general for you.",
1484
- toolCall: {
1485
- name: "slack_send_message",
1486
- params: {
1487
- channel_id: "C04GENERAL1",
1488
- text: "✅ Deploy complete! All services are healthy and running on v2.4.1."
1489
- },
1490
- result: {
1491
- success: true,
1492
- channel: "#general",
1493
- timestamp: "1705612847.293847",
1494
- permalink: "https://acme-corp.slack.com/archives/C04GENERAL1/p1705612847293847"
1495
- }
1496
- },
1497
- claudeSummary: "Done! I've posted your update to <strong>#general</strong>:\n\n<em>\"✅ Deploy complete! All services are healthy and running on v2.4.1.\"</em>\n\nThe message was sent at <strong>2:34 PM</strong>. Would you like me to add any follow-up or tag specific people?"
1498
- },
1499
-
1500
- multi: {
1501
- userMessage: "Find the latest deploy thread and summarize it to #updates",
1502
- claudeIntro: "I'll search for the deploy thread, then post a summary to #updates.",
1503
- isMultiTool: true,
1504
- toolCalls: [
1505
- {
1506
- name: "slack_search_messages",
1507
- params: {
1508
- query: "deploy thread",
1509
- count: 5
1510
- },
1511
- result: [
1512
- { channel: "#engineering", user: "DevOps Bot", time: "Today, 11:42 AM", text: "🚀 Deploy v2.4.1 starting..." },
1513
- { channel: "#engineering", user: "Sarah Chen", time: "Today, 11:45 AM", text: "All tests passing, proceeding with rollout" }
1514
- ]
1515
- },
1516
- {
1517
- name: "slack_send_message",
1518
- params: {
1519
- channel_id: "C04UPDATES2K",
1520
- text: "📋 Deploy Summary (v2.4.1):\n• Started: 11:42 AM\n• Status: Complete\n• Tests: All passing"
1521
- },
1522
- result: {
1523
- success: true,
1524
- channel: "#updates",
1525
- timestamp: "1705614000.123456"
1526
- }
1527
- }
1528
- ],
1529
- claudeSummary: "Done! I found the deploy thread from this morning and posted a summary to <strong>#updates</strong>:\n\n<em>\"📋 Deploy Summary (v2.4.1): Started 11:42 AM, Status: Complete, Tests: All passing\"</em>\n\nThe deploy was led by <strong>Sarah Chen</strong> and completed successfully."
1530
- }
1531
- };
1532
-
1533
- // ═══════════════════════════════════════════════════════════════
1534
- // Animation Helpers
1535
- // ═══════════════════════════════════════════════════════════════
1536
- let speedMultiplier = 1;
1537
- let currentScenario = 'search';
1538
-
1539
- const sleep = ms => new Promise(r => setTimeout(r, ms / speedMultiplier));
1540
-
1541
- function updateSpeed(value) {
1542
- speedMultiplier = parseFloat(value);
1543
- }
1544
-
1545
- function replayScenario() {
1546
- runScenario(currentScenario);
1547
- }
1548
-
1549
- async function typeText(element, text, speed = 25) {
1550
- // Convert \n\n to proper line breaks for final display
1551
- const formattedText = text.replace(/\n\n/g, '<br><br>').replace(/\n/g, '<br>');
1552
-
1553
- // For short text or fast speed, just fade in
1554
- if (speedMultiplier >= 1.5 || text.length < 20) {
1555
- element.style.opacity = '0';
1556
- element.innerHTML = formattedText;
1557
- element.style.transition = 'opacity 0.3s ease-out';
1558
- await sleep(50);
1559
- element.style.opacity = '1';
1560
- return;
1561
- }
1562
-
1563
- // Character-by-character typing with cursor
1564
- element.innerHTML = '<span class="typing-cursor"></span>';
1565
- const cursor = element.querySelector('.typing-cursor');
1566
-
1567
- // Parse HTML and type text content while preserving tags
1568
- const tempDiv = document.createElement('div');
1569
- tempDiv.innerHTML = formattedText;
1570
- const plainText = tempDiv.textContent;
1571
-
1572
- let currentIndex = 0;
1573
- const textSpan = document.createElement('span');
1574
- element.insertBefore(textSpan, cursor);
1575
-
1576
- for (const char of plainText) {
1577
- textSpan.textContent += char;
1578
- await sleep(speed);
1579
- }
1580
-
1581
- // Replace with formatted HTML and remove cursor
1582
- await sleep(100);
1583
- element.innerHTML = formattedText;
1584
- }
1585
-
1586
- // Claude's sparkle icon SVG
1587
- const claudeIcon = `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L9.5 9.5L2 12L9.5 14.5L12 22L14.5 14.5L22 12L14.5 9.5L12 2Z"/></svg>`;
1588
-
1589
- function createMessage(type, sender, time) {
1590
- const msg = document.createElement('div');
1591
- msg.className = `message ${type}`;
1592
- const avatar = type === 'user' ? 'Y' : claudeIcon;
1593
- msg.innerHTML = `
1594
- <div class="message-header">
1595
- <div class="message-avatar">${avatar}</div>
1596
- <span class="message-sender">${sender}</span>
1597
- <span class="message-time">${time}</span>
1598
- </div>
1599
- <div class="message-content"></div>
1600
- `;
1601
- return msg;
1602
- }
1603
-
1604
- function createTypingIndicator() {
1605
- const indicator = document.createElement('div');
1606
- indicator.className = 'message claude';
1607
- indicator.id = 'typingIndicator';
1608
- indicator.innerHTML = `
1609
- <div class="message-header">
1610
- <div class="message-avatar">${claudeIcon}</div>
1611
- <span class="message-sender">Claude</span>
1612
- </div>
1613
- <div class="typing-indicator">
1614
- <div class="typing-dots">
1615
- <div class="typing-dot"></div>
1616
- <div class="typing-dot"></div>
1617
- <div class="typing-dot"></div>
1618
- </div>
1619
- </div>
1620
- `;
1621
- return indicator;
1622
- }
1623
-
1624
- function createToolCall(tool, isRunning = true) {
1625
- const toolEl = document.createElement('div');
1626
- toolEl.className = 'tool-call';
1627
-
1628
- let resultHtml = '';
1629
- if (Array.isArray(tool.result)) {
1630
- resultHtml = tool.result.map(item => {
1631
- if (item.channel) {
1632
- return `<div class="result-item">
1633
- <span class="result-channel">${item.channel}</span> ·
1634
- <span class="result-user">${item.user}</span> ·
1635
- <span class="result-time">${item.time}</span><br>
1636
- <em>"${item.text}"</em>
1637
- </div>`;
1638
- } else if (item.name) {
1639
- return `<div class="result-item">
1640
- <span class="result-user">${item.name}</span> ·
1641
- <span class="result-time">${item.lastMessage}</span><br>
1642
- <em>"${item.preview}"</em>
1643
- </div>`;
1644
- } else {
1645
- return `<div class="result-item">
1646
- <span class="result-user">${item.user}</span> ·
1647
- <span class="result-time">${item.time}</span><br>
1648
- "${item.text}"
1649
- </div>`;
1650
- }
1651
- }).join('');
1652
- } else if (tool.result.success) {
1653
- resultHtml = `<div class="result-item">
1654
- ✅ Message sent to <span class="result-channel">${tool.result.channel}</span>
1655
- </div>`;
1656
- }
1657
-
1658
- const statusClass = isRunning ? 'running' : 'success';
1659
- const statusText = isRunning ? 'Running...' : 'Complete';
1660
-
1661
- toolEl.innerHTML = `
1662
- <div class="tool-header" onclick="this.parentElement.classList.toggle('expanded')">
1663
- <span class="tool-icon">🔧</span>
1664
- <span class="tool-name">${tool.name}</span>
1665
- <span class="tool-status ${statusClass}">${statusText}</span>
1666
- <span class="tool-chevron">▼</span>
1667
- </div>
1668
- <div class="tool-body">
1669
- <div class="tool-section">
1670
- <div class="tool-section-label">Input</div>
1671
- <div class="tool-params">${JSON.stringify(tool.params, null, 2)}</div>
1672
- </div>
1673
- <div class="tool-section">
1674
- <div class="tool-section-label">Output</div>
1675
- <div class="tool-result">${resultHtml}</div>
1676
- </div>
1677
- </div>
1678
- `;
1679
- return toolEl;
1680
- }
1681
-
1682
- function updateToolStatus(toolEl, isComplete) {
1683
- const statusEl = toolEl.querySelector('.tool-status');
1684
- statusEl.className = `tool-status ${isComplete ? 'success' : 'running'}`;
1685
- statusEl.textContent = isComplete ? 'Complete' : 'Running...';
1686
- }
1687
-
1688
- // ═══════════════════════════════════════════════════════════════
1689
- // Run Scenario
1690
- // ═══════════════════════════════════════════════════════════════
1691
- let isRunning = false;
1692
-
1693
- async function runScenario(scenarioId) {
1694
- if (isRunning) return;
1695
- isRunning = true;
1696
- currentScenario = scenarioId;
1697
-
1698
- // Update button states
1699
- const replayBtn = document.getElementById('replayBtn');
1700
- if (replayBtn) replayBtn.disabled = true;
1701
-
1702
- document.querySelectorAll('.scenario-btn').forEach(btn => {
1703
- const isActive = btn.dataset.scenario === scenarioId;
1704
- btn.classList.toggle('active', isActive);
1705
- btn.classList.toggle('playing', isActive);
1706
- btn.setAttribute('aria-selected', isActive ? 'true' : 'false');
1707
- });
1708
-
1709
- const container = document.getElementById('chatContainer');
1710
-
1711
- // Show scenario caption
1712
- const captions = {
1713
- search: "🔍 Searching Messages",
1714
- thread: "📜 Reading Thread",
1715
- list: "💬 Listing DMs",
1716
- send: "✉️ Sending Message",
1717
- multi: "🔗 Multi-Tool Workflow"
1718
- };
1719
- const caption = document.getElementById('scenarioCaption');
1720
- caption.textContent = captions[scenarioId] || scenarioId;
1721
- caption.classList.add('visible');
1722
- setTimeout(() => caption.classList.remove('visible'), 2000);
1723
-
1724
- // Smooth transition: fade out, clear, fade in
1725
- container.classList.add('fading');
1726
- await sleep(300);
1727
- container.innerHTML = '';
1728
- container.classList.remove('fading');
1729
-
1730
- const scenario = scenarios[scenarioId];
1731
- const now = new Date();
1732
- const timeStr = now.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
1733
-
1734
- // 1. User message appears
1735
- const userMsg = createMessage('user', 'You', timeStr);
1736
- container.appendChild(userMsg);
1737
- userMsg.querySelector('.message-content').textContent = scenario.userMessage;
1738
- container.scrollTop = container.scrollHeight;
1739
-
1740
- await sleep(600);
1741
-
1742
- // 2. Claude starts typing
1743
- const typing = createTypingIndicator();
1744
- container.appendChild(typing);
1745
- container.scrollTop = container.scrollHeight;
1746
-
1747
- await sleep(1200);
1748
-
1749
- // 3. Claude's intro
1750
- typing.remove();
1751
- const claudeMsg = createMessage('claude', 'Claude', timeStr);
1752
- container.appendChild(claudeMsg);
1753
- const contentEl = claudeMsg.querySelector('.message-content');
1754
- await typeText(contentEl, scenario.claudeIntro);
1755
- container.scrollTop = container.scrollHeight;
1756
-
1757
- await sleep(700);
1758
-
1759
- // 4. Tool call(s) - handle single or multi-tool scenarios
1760
- if (scenario.isMultiTool && scenario.toolCalls) {
1761
- // Multi-tool scenario
1762
- for (let i = 0; i < scenario.toolCalls.length; i++) {
1763
- const tool = scenario.toolCalls[i];
1764
- const toolCall = createToolCall(tool, true);
1765
- contentEl.appendChild(toolCall);
1766
- container.scrollTop = container.scrollHeight;
1767
-
1768
- await sleep(400);
1769
- toolCall.classList.add('expanded');
1770
- container.scrollTop = container.scrollHeight;
1771
-
1772
- await sleep(1200);
1773
- updateToolStatus(toolCall, true);
1774
- container.scrollTop = container.scrollHeight;
1775
-
1776
- if (i < scenario.toolCalls.length - 1) {
1777
- await sleep(600); // Pause between tools
1778
- }
1779
- }
1780
- } else {
1781
- // Single tool scenario
1782
- const toolCall = createToolCall(scenario.toolCall, true);
1783
- contentEl.appendChild(toolCall);
1784
- container.scrollTop = container.scrollHeight;
1785
-
1786
- await sleep(400);
1787
- toolCall.classList.add('expanded');
1788
- container.scrollTop = container.scrollHeight;
1789
-
1790
- await sleep(1500);
1791
- updateToolStatus(toolCall, true);
1792
- container.scrollTop = container.scrollHeight;
1793
- }
1794
-
1795
- await sleep(500);
1796
-
1797
- // 5. Claude's summary
1798
- const summaryP = document.createElement('div');
1799
- summaryP.style.marginTop = '16px';
1800
- contentEl.appendChild(summaryP);
1801
- await typeText(summaryP, scenario.claudeSummary);
1802
- container.scrollTop = container.scrollHeight;
1803
-
1804
- // Cleanup
1805
- document.querySelectorAll('.scenario-btn').forEach(btn => {
1806
- btn.classList.remove('playing');
1807
- });
1808
- if (replayBtn) replayBtn.disabled = false;
1809
- isRunning = false;
1810
- }
1811
-
1812
- // ═══════════════════════════════════════════════════════════════
1813
- // Auto-Play All Scenarios
1814
- // ═══════════════════════════════════════════════════════════════
1815
- let isAutoPlaying = false;
1816
-
1817
- async function autoPlayAll() {
1818
- if (isAutoPlaying || isRunning) return;
1819
- isAutoPlaying = true;
1820
-
1821
- const autoPlayBtn = document.getElementById('autoPlayBtn');
1822
- if (autoPlayBtn) {
1823
- autoPlayBtn.innerHTML = '<span>⏹</span><span>Stop</span>';
1824
- autoPlayBtn.onclick = stopAutoPlay;
1825
- }
1826
-
1827
- // Show title card for 3s before starting
1828
- const titleCard = document.getElementById('titleCard');
1829
- const chatContainer = document.getElementById('chatContainer');
1830
- chatContainer.style.display = 'none';
1831
- titleCard.classList.add('visible');
1832
- await sleep(3000);
1833
- titleCard.classList.remove('visible');
1834
- await sleep(500); // Fade transition
1835
- chatContainer.style.display = '';
1836
-
1837
- const scenarioOrder = ['search', 'thread', 'list', 'send', 'multi'];
1838
-
1839
- for (let i = 0; i < scenarioOrder.length; i++) {
1840
- if (!isAutoPlaying) break;
1841
- updateProgress(i + 1, scenarioOrder.length);
1842
- await runScenario(scenarioOrder[i]);
1843
- if (!isAutoPlaying) break;
1844
- await sleep(2000); // Pause between scenarios
1845
- }
1846
- updateProgress(0, 0); // Clear progress
1847
-
1848
- // Show closing card for 4s after completion (only if not stopped)
1849
- if (isAutoPlaying) {
1850
- const closingCard = document.getElementById('closingCard');
1851
- chatContainer.style.display = 'none';
1852
- await sleep(500);
1853
- closingCard.classList.add('visible');
1854
- await sleep(4000);
1855
- closingCard.classList.remove('visible');
1856
- await sleep(500);
1857
- chatContainer.style.display = '';
1858
- }
1859
-
1860
- stopAutoPlay();
1861
- }
1862
-
1863
- function stopAutoPlay() {
1864
- isAutoPlaying = false;
1865
- const autoPlayBtn = document.getElementById('autoPlayBtn');
1866
- if (autoPlayBtn) {
1867
- autoPlayBtn.innerHTML = '<span>▶</span><span>Auto-Play All</span>';
1868
- autoPlayBtn.onclick = autoPlayAll;
1869
- }
1870
- updateProgress(0, 0);
1871
- }
1872
-
1873
- function updateProgress(current, total) {
1874
- const indicator = document.getElementById('progressIndicator');
1875
- if (!indicator) return;
1876
-
1877
- if (current === 0 || total === 0) {
1878
- indicator.style.display = 'none';
1879
- return;
1880
- }
1881
-
1882
- indicator.style.display = 'flex';
1883
- indicator.querySelector('.progress-text').textContent = `${current}/${total}`;
1884
- indicator.querySelector('.progress-fill').style.width = `${(current / total) * 100}%`;
1885
- }
1886
-
1887
- async function copyShareLink() {
1888
- const btn = document.querySelector('.share-btn');
1889
- const url = window.location.href.split('?')[0]; // Remove any query params
1890
-
1891
- try {
1892
- await navigator.clipboard.writeText(url);
1893
- btn.classList.add('copied');
1894
- btn.querySelector('.share-text').textContent = 'Copied!';
1895
- btn.querySelector('.share-icon').textContent = '✓';
1896
-
1897
- setTimeout(() => {
1898
- btn.classList.remove('copied');
1899
- btn.querySelector('.share-text').textContent = 'Share';
1900
- btn.querySelector('.share-icon').textContent = '🔗';
1901
- }, 2000);
1902
- } catch (err) {
1903
- // Fallback for older browsers
1904
- const textarea = document.createElement('textarea');
1905
- textarea.value = url;
1906
- document.body.appendChild(textarea);
1907
- textarea.select();
1908
- document.execCommand('copy');
1909
- document.body.removeChild(textarea);
1910
- }
1911
- }
1912
-
1913
- // ═══════════════════════════════════════════════════════════════
1914
- // Keyboard Shortcuts
1915
- // ═══════════════════════════════════════════════════════════════
1916
- function toggleFullscreen() {
1917
- document.body.classList.toggle('fullscreen-mode');
1918
- }
1919
-
1920
- document.addEventListener('keydown', (e) => {
1921
- // Fullscreen can toggle anytime
1922
- if (e.key === 'f' || e.key === 'F') {
1923
- toggleFullscreen();
1924
- return;
1925
- }
1926
-
1927
- // Escape exits fullscreen first, then stops auto-play
1928
- if (e.key === 'Escape') {
1929
- if (document.body.classList.contains('fullscreen-mode')) {
1930
- toggleFullscreen();
1931
- } else {
1932
- stopAutoPlay();
1933
- }
1934
- return;
1935
- }
1936
-
1937
- if (isRunning) return;
1938
-
1939
- switch(e.key) {
1940
- case '1': runScenario('search'); break;
1941
- case '2': runScenario('thread'); break;
1942
- case '3': runScenario('list'); break;
1943
- case '4': runScenario('send'); break;
1944
- case '5': runScenario('multi'); break;
1945
- case 'r': case 'R': replayScenario(); break;
1946
- case 'a': case 'A': autoPlayAll(); break;
1947
- }
1948
- });
1949
-
1950
- // ═══════════════════════════════════════════════════════════════
1951
- // Initialize
1952
- // ═══════════════════════════════════════════════════════════════
1953
- document.addEventListener('DOMContentLoaded', () => {
1954
- runScenario('search');
1955
- });
1956
- </script>
1957
- </body>
1958
- </html>