Yury Semikhatsky commited on
Commit
45fee73
·
unverified ·
1 Parent(s): dc34190

feat(extension): bypass connection dialog when token is provided (#1024)

Browse files

<img width="698" height="551" alt="image"
src="https://github.com/user-attachments/assets/feee2375-7654-42cb-b9fa-f48aee5dd045"
/>


Reference: https://github.com/microsoft/playwright-mcp/issues/965

extension/src/ui/authToken.css ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ Copyright (c) Microsoft Corporation.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ .auth-token-section {
18
+ margin: 16px 0;
19
+ padding: 16px;
20
+ background-color: #f6f8fa;
21
+ border-radius: 6px;
22
+ }
23
+
24
+ .auth-token-description {
25
+ font-size: 12px;
26
+ color: #656d76;
27
+ margin-bottom: 12px;
28
+ }
29
+
30
+ .auth-token-container {
31
+ display: flex;
32
+ align-items: center;
33
+ gap: 8px;
34
+ background-color: #ffffff;
35
+ padding: 8px;
36
+ }
37
+
38
+ .auth-token-code {
39
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
40
+ font-size: 12px;
41
+ color: #1f2328;
42
+ border: none;
43
+ flex: 1;
44
+ padding: 0;
45
+ word-break: break-all;
46
+ }
47
+
48
+ .auth-token-refresh {
49
+ flex: none;
50
+ height: 24px;
51
+ width: 24px;
52
+ border: none;
53
+ outline: none;
54
+ color: var(--color-fg-muted);
55
+ background: transparent;
56
+ padding: 4px;
57
+ cursor: pointer;
58
+ display: inline-flex;
59
+ align-items: center;
60
+ justify-content: center;
61
+ border-radius: 4px;
62
+ }
63
+
64
+ .auth-token-refresh svg {
65
+ margin: 0;
66
+ }
67
+
68
+ .auth-token-refresh:not(:disabled):hover {
69
+ background-color: var(--color-btn-selected-bg);
70
+ }
extension/src/ui/authToken.tsx ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import React, { useCallback, useState } from 'react';
18
+ import { CopyToClipboard } from './copyToClipboard';
19
+ import * as icons from './icons';
20
+ import './authToken.css';
21
+
22
+ export const AuthTokenSection: React.FC<{}> = ({}) => {
23
+ const [authToken, setAuthToken] = useState<string>(getOrCreateAuthToken);
24
+
25
+ const onRegenerateToken = useCallback(() => {
26
+ const newToken = generateAuthToken();
27
+ localStorage.setItem('auth-token', newToken);
28
+ setAuthToken(newToken);
29
+ }, []);
30
+
31
+ return (
32
+ <div className='auth-token-section'>
33
+ <div className='auth-token-description'>
34
+ Set this environment variable to bypass the connection dialog:
35
+ </div>
36
+ <div className='auth-token-container'>
37
+ <code className='auth-token-code'>PLAYWRIGHT_MCP_EXTENSION_TOKEN={authToken}</code>
38
+ <button className='auth-token-refresh' title='Generate new token' aria-label='Generate new token'onClick={onRegenerateToken}>{icons.refresh()}</button>
39
+ <CopyToClipboard value={authToken} />
40
+ </div>
41
+ </div>
42
+ );
43
+ };
44
+
45
+ function generateAuthToken(): string {
46
+ // Generate a cryptographically secure random token
47
+ const array = new Uint8Array(32);
48
+ crypto.getRandomValues(array);
49
+ // Convert to base64 and make it URL-safe
50
+ return btoa(String.fromCharCode.apply(null, Array.from(array)))
51
+ .replace(/[+/=]/g, match => {
52
+ switch (match) {
53
+ case '+': return '-';
54
+ case '/': return '_';
55
+ case '=': return '';
56
+ default: return match;
57
+ }
58
+ });
59
+ }
60
+
61
+ export const getOrCreateAuthToken = (): string => {
62
+ let token = localStorage.getItem('auth-token');
63
+ if (!token) {
64
+ token = generateAuthToken();
65
+ localStorage.setItem('auth-token', token);
66
+ }
67
+ return token;
68
+ }
extension/src/ui/colors.css ADDED
@@ -0,0 +1,891 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 GitHub Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE. */
22
+
23
+ :root {
24
+ --color-canvas-default-transparent: rgba(255,255,255,0);
25
+ --color-marketing-icon-primary: #218bff;
26
+ --color-marketing-icon-secondary: #54aeff;
27
+ --color-diff-blob-addition-num-text: #24292f;
28
+ --color-diff-blob-addition-fg: #24292f;
29
+ --color-diff-blob-addition-num-bg: #CCFFD8;
30
+ --color-diff-blob-addition-line-bg: #E6FFEC;
31
+ --color-diff-blob-addition-word-bg: #ABF2BC;
32
+ --color-diff-blob-deletion-num-text: #24292f;
33
+ --color-diff-blob-deletion-fg: #24292f;
34
+ --color-diff-blob-deletion-num-bg: #FFD7D5;
35
+ --color-diff-blob-deletion-line-bg: #FFEBE9;
36
+ --color-diff-blob-deletion-word-bg: rgba(255,129,130,0.4);
37
+ --color-diff-blob-hunk-num-bg: rgba(84,174,255,0.4);
38
+ --color-diff-blob-expander-icon: #57606a;
39
+ --color-diff-blob-selected-line-highlight-mix-blend-mode: multiply;
40
+ --color-diffstat-deletion-border: rgba(27,31,36,0.15);
41
+ --color-diffstat-addition-border: rgba(27,31,36,0.15);
42
+ --color-diffstat-addition-bg: #2da44e;
43
+ --color-search-keyword-hl: #fff8c5;
44
+ --color-prettylights-syntax-comment: #6e7781;
45
+ --color-prettylights-syntax-constant: #0550ae;
46
+ --color-prettylights-syntax-entity: #8250df;
47
+ --color-prettylights-syntax-storage-modifier-import: #24292f;
48
+ --color-prettylights-syntax-entity-tag: #116329;
49
+ --color-prettylights-syntax-keyword: #cf222e;
50
+ --color-prettylights-syntax-string: #0a3069;
51
+ --color-prettylights-syntax-variable: #953800;
52
+ --color-prettylights-syntax-brackethighlighter-unmatched: #82071e;
53
+ --color-prettylights-syntax-invalid-illegal-text: #f6f8fa;
54
+ --color-prettylights-syntax-invalid-illegal-bg: #82071e;
55
+ --color-prettylights-syntax-carriage-return-text: #f6f8fa;
56
+ --color-prettylights-syntax-carriage-return-bg: #cf222e;
57
+ --color-prettylights-syntax-string-regexp: #116329;
58
+ --color-prettylights-syntax-markup-list: #3b2300;
59
+ --color-prettylights-syntax-markup-heading: #0550ae;
60
+ --color-prettylights-syntax-markup-italic: #24292f;
61
+ --color-prettylights-syntax-markup-bold: #24292f;
62
+ --color-prettylights-syntax-markup-deleted-text: #82071e;
63
+ --color-prettylights-syntax-markup-deleted-bg: #FFEBE9;
64
+ --color-prettylights-syntax-markup-inserted-text: #116329;
65
+ --color-prettylights-syntax-markup-inserted-bg: #dafbe1;
66
+ --color-prettylights-syntax-markup-changed-text: #953800;
67
+ --color-prettylights-syntax-markup-changed-bg: #ffd8b5;
68
+ --color-prettylights-syntax-markup-ignored-text: #eaeef2;
69
+ --color-prettylights-syntax-markup-ignored-bg: #0550ae;
70
+ --color-prettylights-syntax-meta-diff-range: #8250df;
71
+ --color-prettylights-syntax-brackethighlighter-angle: #57606a;
72
+ --color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;
73
+ --color-prettylights-syntax-constant-other-reference-link: #0a3069;
74
+ --color-codemirror-text: #24292f;
75
+ --color-codemirror-bg: #ffffff;
76
+ --color-codemirror-gutters-bg: #ffffff;
77
+ --color-codemirror-guttermarker-text: #ffffff;
78
+ --color-codemirror-guttermarker-subtle-text: #6e7781;
79
+ --color-codemirror-linenumber-text: #57606a;
80
+ --color-codemirror-cursor: #24292f;
81
+ --color-codemirror-selection-bg: rgba(84,174,255,0.4);
82
+ --color-codemirror-activeline-bg: rgba(234,238,242,0.5);
83
+ --color-codemirror-matchingbracket-text: #24292f;
84
+ --color-codemirror-lines-bg: #ffffff;
85
+ --color-codemirror-syntax-comment: #24292f;
86
+ --color-codemirror-syntax-constant: #0550ae;
87
+ --color-codemirror-syntax-entity: #8250df;
88
+ --color-codemirror-syntax-keyword: #cf222e;
89
+ --color-codemirror-syntax-storage: #cf222e;
90
+ --color-codemirror-syntax-string: #0a3069;
91
+ --color-codemirror-syntax-support: #0550ae;
92
+ --color-codemirror-syntax-variable: #953800;
93
+ --color-checks-bg: #24292f;
94
+ --color-checks-run-border-width: 0px;
95
+ --color-checks-container-border-width: 0px;
96
+ --color-checks-text-primary: #f6f8fa;
97
+ --color-checks-text-secondary: #8c959f;
98
+ --color-checks-text-link: #54aeff;
99
+ --color-checks-btn-icon: #afb8c1;
100
+ --color-checks-btn-hover-icon: #f6f8fa;
101
+ --color-checks-btn-hover-bg: rgba(255,255,255,0.125);
102
+ --color-checks-input-text: #eaeef2;
103
+ --color-checks-input-placeholder-text: #8c959f;
104
+ --color-checks-input-focus-text: #8c959f;
105
+ --color-checks-input-bg: #32383f;
106
+ --color-checks-input-shadow: none;
107
+ --color-checks-donut-error: #fa4549;
108
+ --color-checks-donut-pending: #bf8700;
109
+ --color-checks-donut-success: #2da44e;
110
+ --color-checks-donut-neutral: #afb8c1;
111
+ --color-checks-dropdown-text: #afb8c1;
112
+ --color-checks-dropdown-bg: #32383f;
113
+ --color-checks-dropdown-border: #424a53;
114
+ --color-checks-dropdown-shadow: rgba(27,31,36,0.3);
115
+ --color-checks-dropdown-hover-text: #f6f8fa;
116
+ --color-checks-dropdown-hover-bg: #424a53;
117
+ --color-checks-dropdown-btn-hover-text: #f6f8fa;
118
+ --color-checks-dropdown-btn-hover-bg: #32383f;
119
+ --color-checks-scrollbar-thumb-bg: #57606a;
120
+ --color-checks-header-label-text: #d0d7de;
121
+ --color-checks-header-label-open-text: #f6f8fa;
122
+ --color-checks-header-border: #32383f;
123
+ --color-checks-header-icon: #8c959f;
124
+ --color-checks-line-text: #d0d7de;
125
+ --color-checks-line-num-text: rgba(140,149,159,0.75);
126
+ --color-checks-line-timestamp-text: #8c959f;
127
+ --color-checks-line-hover-bg: #32383f;
128
+ --color-checks-line-selected-bg: rgba(33,139,255,0.15);
129
+ --color-checks-line-selected-num-text: #54aeff;
130
+ --color-checks-line-dt-fm-text: #24292f;
131
+ --color-checks-line-dt-fm-bg: #9a6700;
132
+ --color-checks-gate-bg: rgba(125,78,0,0.15);
133
+ --color-checks-gate-text: #d0d7de;
134
+ --color-checks-gate-waiting-text: #afb8c1;
135
+ --color-checks-step-header-open-bg: #32383f;
136
+ --color-checks-step-error-text: #ff8182;
137
+ --color-checks-step-warning-text: #d4a72c;
138
+ --color-checks-logline-text: #8c959f;
139
+ --color-checks-logline-num-text: rgba(140,149,159,0.75);
140
+ --color-checks-logline-debug-text: #c297ff;
141
+ --color-checks-logline-error-text: #d0d7de;
142
+ --color-checks-logline-error-num-text: #ff8182;
143
+ --color-checks-logline-error-bg: rgba(164,14,38,0.15);
144
+ --color-checks-logline-warning-text: #d0d7de;
145
+ --color-checks-logline-warning-num-text: #d4a72c;
146
+ --color-checks-logline-warning-bg: rgba(125,78,0,0.15);
147
+ --color-checks-logline-command-text: #54aeff;
148
+ --color-checks-logline-section-text: #4ac26b;
149
+ --color-checks-ansi-black: #24292f;
150
+ --color-checks-ansi-black-bright: #32383f;
151
+ --color-checks-ansi-white: #d0d7de;
152
+ --color-checks-ansi-white-bright: #d0d7de;
153
+ --color-checks-ansi-gray: #8c959f;
154
+ --color-checks-ansi-red: #ff8182;
155
+ --color-checks-ansi-red-bright: #ffaba8;
156
+ --color-checks-ansi-green: #4ac26b;
157
+ --color-checks-ansi-green-bright: #6fdd8b;
158
+ --color-checks-ansi-yellow: #d4a72c;
159
+ --color-checks-ansi-yellow-bright: #eac54f;
160
+ --color-checks-ansi-blue: #54aeff;
161
+ --color-checks-ansi-blue-bright: #80ccff;
162
+ --color-checks-ansi-magenta: #c297ff;
163
+ --color-checks-ansi-magenta-bright: #d8b9ff;
164
+ --color-checks-ansi-cyan: #76e3ea;
165
+ --color-checks-ansi-cyan-bright: #b3f0ff;
166
+ --color-project-header-bg: #24292f;
167
+ --color-project-sidebar-bg: #ffffff;
168
+ --color-project-gradient-in: #ffffff;
169
+ --color-project-gradient-out: rgba(255,255,255,0);
170
+ --color-mktg-success: rgba(36,146,67,1);
171
+ --color-mktg-info: rgba(19,119,234,1);
172
+ --color-mktg-bg-shade-gradient-top: rgba(27,31,36,0.065);
173
+ --color-mktg-bg-shade-gradient-bottom: rgba(27,31,36,0);
174
+ --color-mktg-btn-bg-top: hsla(228,82%,66%,1);
175
+ --color-mktg-btn-bg-bottom: #4969ed;
176
+ --color-mktg-btn-bg-overlay-top: hsla(228,74%,59%,1);
177
+ --color-mktg-btn-bg-overlay-bottom: #3355e0;
178
+ --color-mktg-btn-text: #ffffff;
179
+ --color-mktg-btn-primary-bg-top: hsla(137,56%,46%,1);
180
+ --color-mktg-btn-primary-bg-bottom: #2ea44f;
181
+ --color-mktg-btn-primary-bg-overlay-top: hsla(134,60%,38%,1);
182
+ --color-mktg-btn-primary-bg-overlay-bottom: #22863a;
183
+ --color-mktg-btn-primary-text: #ffffff;
184
+ --color-mktg-btn-enterprise-bg-top: hsla(249,100%,72%,1);
185
+ --color-mktg-btn-enterprise-bg-bottom: #6f57ff;
186
+ --color-mktg-btn-enterprise-bg-overlay-top: hsla(248,65%,63%,1);
187
+ --color-mktg-btn-enterprise-bg-overlay-bottom: #614eda;
188
+ --color-mktg-btn-enterprise-text: #ffffff;
189
+ --color-mktg-btn-outline-text: #4969ed;
190
+ --color-mktg-btn-outline-border: rgba(73,105,237,0.3);
191
+ --color-mktg-btn-outline-hover-text: #3355e0;
192
+ --color-mktg-btn-outline-hover-border: rgba(51,85,224,0.5);
193
+ --color-mktg-btn-outline-focus-border: #4969ed;
194
+ --color-mktg-btn-outline-focus-border-inset: rgba(73,105,237,0.5);
195
+ --color-mktg-btn-dark-text: #ffffff;
196
+ --color-mktg-btn-dark-border: rgba(255,255,255,0.3);
197
+ --color-mktg-btn-dark-hover-text: #ffffff;
198
+ --color-mktg-btn-dark-hover-border: rgba(255,255,255,0.5);
199
+ --color-mktg-btn-dark-focus-border: #ffffff;
200
+ --color-mktg-btn-dark-focus-border-inset: rgba(255,255,255,0.5);
201
+ --color-avatar-bg: #ffffff;
202
+ --color-avatar-border: rgba(27,31,36,0.15);
203
+ --color-avatar-stack-fade: #afb8c1;
204
+ --color-avatar-stack-fade-more: #d0d7de;
205
+ --color-avatar-child-shadow: -2px -2px 0 rgba(255,255,255,0.8);
206
+ --color-topic-tag-border: rgba(0,0,0,0);
207
+ --color-select-menu-backdrop-border: rgba(0,0,0,0);
208
+ --color-select-menu-tap-highlight: rgba(175,184,193,0.5);
209
+ --color-select-menu-tap-focus-bg: #b6e3ff;
210
+ --color-overlay-shadow: 0 1px 3px rgba(27,31,36,0.12), 0 8px 24px rgba(66,74,83,0.12);
211
+ --color-header-text: rgba(255,255,255,0.7);
212
+ --color-header-bg: #24292f;
213
+ --color-header-logo: #ffffff;
214
+ --color-header-search-bg: #24292f;
215
+ --color-header-search-border: #57606a;
216
+ --color-sidenav-selected-bg: #ffffff;
217
+ --color-menu-bg-active: rgba(0,0,0,0);
218
+ --color-control-transparent-bg-hover: #818b981a;
219
+ --color-input-disabled-bg: rgba(175,184,193,0.2);
220
+ --color-timeline-badge-bg: #eaeef2;
221
+ --color-ansi-black: #24292f;
222
+ --color-ansi-black-bright: #57606a;
223
+ --color-ansi-white: #6e7781;
224
+ --color-ansi-white-bright: #8c959f;
225
+ --color-ansi-gray: #6e7781;
226
+ --color-ansi-red: #cf222e;
227
+ --color-ansi-red-bright: #a40e26;
228
+ --color-ansi-green: #116329;
229
+ --color-ansi-green-bright: #1a7f37;
230
+ --color-ansi-yellow: #4d2d00;
231
+ --color-ansi-yellow-bright: #633c01;
232
+ --color-ansi-blue: #0969da;
233
+ --color-ansi-blue-bright: #218bff;
234
+ --color-ansi-magenta: #8250df;
235
+ --color-ansi-magenta-bright: #a475f9;
236
+ --color-ansi-cyan: #1b7c83;
237
+ --color-ansi-cyan-bright: #3192aa;
238
+ --color-btn-text: #24292f;
239
+ --color-btn-bg: #f6f8fa;
240
+ --color-btn-border: rgba(27,31,36,0.15);
241
+ --color-btn-shadow: 0 1px 0 rgba(27,31,36,0.04);
242
+ --color-btn-inset-shadow: inset 0 1px 0 rgba(255,255,255,0.25);
243
+ --color-btn-hover-bg: #f3f4f6;
244
+ --color-btn-hover-border: rgba(27,31,36,0.15);
245
+ --color-btn-active-bg: hsla(220,14%,93%,1);
246
+ --color-btn-active-border: rgba(27,31,36,0.15);
247
+ --color-btn-selected-bg: hsla(220,14%,94%,1);
248
+ --color-btn-focus-bg: #f6f8fa;
249
+ --color-btn-focus-border: rgba(27,31,36,0.15);
250
+ --color-btn-focus-shadow: 0 0 0 3px rgba(9,105,218,0.3);
251
+ --color-btn-shadow-active: inset 0 0.15em 0.3em rgba(27,31,36,0.15);
252
+ --color-btn-shadow-input-focus: 0 0 0 0.2em rgba(9,105,218,0.3);
253
+ --color-btn-counter-bg: rgba(27,31,36,0.08);
254
+ --color-btn-primary-text: #ffffff;
255
+ --color-btn-primary-bg: #2da44e;
256
+ --color-btn-primary-border: rgba(27,31,36,0.15);
257
+ --color-btn-primary-shadow: 0 1px 0 rgba(27,31,36,0.1);
258
+ --color-btn-primary-inset-shadow: inset 0 1px 0 rgba(255,255,255,0.03);
259
+ --color-btn-primary-hover-bg: #2c974b;
260
+ --color-btn-primary-hover-border: rgba(27,31,36,0.15);
261
+ --color-btn-primary-selected-bg: hsla(137,55%,36%,1);
262
+ --color-btn-primary-selected-shadow: inset 0 1px 0 rgba(0,45,17,0.2);
263
+ --color-btn-primary-disabled-text: rgba(255,255,255,0.8);
264
+ --color-btn-primary-disabled-bg: #94d3a2;
265
+ --color-btn-primary-disabled-border: rgba(27,31,36,0.15);
266
+ --color-btn-primary-focus-bg: #2da44e;
267
+ --color-btn-primary-focus-border: rgba(27,31,36,0.15);
268
+ --color-btn-primary-focus-shadow: 0 0 0 3px rgba(45,164,78,0.4);
269
+ --color-btn-primary-icon: rgba(255,255,255,0.8);
270
+ --color-btn-primary-counter-bg: rgba(255,255,255,0.2);
271
+ --color-btn-outline-text: #0969da;
272
+ --color-btn-outline-hover-text: #ffffff;
273
+ --color-btn-outline-hover-bg: #0969da;
274
+ --color-btn-outline-hover-border: rgba(27,31,36,0.15);
275
+ --color-btn-outline-hover-shadow: 0 1px 0 rgba(27,31,36,0.1);
276
+ --color-btn-outline-hover-inset-shadow: inset 0 1px 0 rgba(255,255,255,0.03);
277
+ --color-btn-outline-hover-counter-bg: rgba(255,255,255,0.2);
278
+ --color-btn-outline-selected-text: #ffffff;
279
+ --color-btn-outline-selected-bg: hsla(212,92%,42%,1);
280
+ --color-btn-outline-selected-border: rgba(27,31,36,0.15);
281
+ --color-btn-outline-selected-shadow: inset 0 1px 0 rgba(0,33,85,0.2);
282
+ --color-btn-outline-disabled-text: rgba(9,105,218,0.5);
283
+ --color-btn-outline-disabled-bg: #f6f8fa;
284
+ --color-btn-outline-disabled-counter-bg: rgba(9,105,218,0.05);
285
+ --color-btn-outline-focus-border: rgba(27,31,36,0.15);
286
+ --color-btn-outline-focus-shadow: 0 0 0 3px rgba(5,80,174,0.4);
287
+ --color-btn-outline-counter-bg: rgba(9,105,218,0.1);
288
+ --color-btn-danger-text: #cf222e;
289
+ --color-btn-danger-hover-text: #ffffff;
290
+ --color-btn-danger-hover-bg: #a40e26;
291
+ --color-btn-danger-hover-border: rgba(27,31,36,0.15);
292
+ --color-btn-danger-hover-shadow: 0 1px 0 rgba(27,31,36,0.1);
293
+ --color-btn-danger-hover-inset-shadow: inset 0 1px 0 rgba(255,255,255,0.03);
294
+ --color-btn-danger-hover-counter-bg: rgba(255,255,255,0.2);
295
+ --color-btn-danger-selected-text: #ffffff;
296
+ --color-btn-danger-selected-bg: hsla(356,72%,44%,1);
297
+ --color-btn-danger-selected-border: rgba(27,31,36,0.15);
298
+ --color-btn-danger-selected-shadow: inset 0 1px 0 rgba(76,0,20,0.2);
299
+ --color-btn-danger-disabled-text: rgba(207,34,46,0.5);
300
+ --color-btn-danger-disabled-bg: #f6f8fa;
301
+ --color-btn-danger-disabled-counter-bg: rgba(207,34,46,0.05);
302
+ --color-btn-danger-focus-border: rgba(27,31,36,0.15);
303
+ --color-btn-danger-focus-shadow: 0 0 0 3px rgba(164,14,38,0.4);
304
+ --color-btn-danger-counter-bg: rgba(207,34,46,0.1);
305
+ --color-btn-danger-icon: #cf222e;
306
+ --color-btn-danger-hover-icon: #ffffff;
307
+ --color-underlinenav-icon: #6e7781;
308
+ --color-underlinenav-border-hover: rgba(175,184,193,0.2);
309
+ --color-fg-default: #24292f;
310
+ --color-fg-muted: #57606a;
311
+ --color-fg-subtle: #6e7781;
312
+ --color-fg-on-emphasis: #ffffff;
313
+ --color-canvas-default: #ffffff;
314
+ --color-canvas-overlay: #ffffff;
315
+ --color-canvas-inset: #f6f8fa;
316
+ --color-canvas-subtle: #f6f8fa;
317
+ --color-border-default: #d0d7de;
318
+ --color-border-muted: hsla(210,18%,87%,1);
319
+ --color-border-subtle: rgba(27,31,36,0.15);
320
+ --color-shadow-small: 0 1px 0 rgba(27,31,36,0.04);
321
+ --color-shadow-medium: 0 3px 6px rgba(140,149,159,0.15);
322
+ --color-shadow-large: 0 8px 24px rgba(140,149,159,0.2);
323
+ --color-shadow-extra-large: 0 12px 28px rgba(140,149,159,0.3);
324
+ --color-neutral-emphasis-plus: #24292f;
325
+ --color-neutral-emphasis: #6e7781;
326
+ --color-neutral-muted: rgba(175,184,193,0.2);
327
+ --color-neutral-subtle: rgba(234,238,242,0.5);
328
+ --color-accent-fg: #0969da;
329
+ --color-accent-emphasis: #0969da;
330
+ --color-accent-muted: rgba(84,174,255,0.4);
331
+ --color-accent-subtle: #ddf4ff;
332
+ --color-success-fg: #1a7f37;
333
+ --color-success-emphasis: #2da44e;
334
+ --color-success-muted: rgba(74,194,107,0.4);
335
+ --color-success-subtle: #dafbe1;
336
+ --color-attention-fg: #9a6700;
337
+ --color-attention-emphasis: #bf8700;
338
+ --color-attention-muted: rgba(212,167,44,0.4);
339
+ --color-attention-subtle: #fff8c5;
340
+ --color-severe-fg: #bc4c00;
341
+ --color-severe-emphasis: #bc4c00;
342
+ --color-severe-muted: rgba(251,143,68,0.4);
343
+ --color-severe-subtle: #fff1e5;
344
+ --color-danger-fg: #cf222e;
345
+ --color-danger-emphasis: #cf222e;
346
+ --color-danger-muted: rgba(255,129,130,0.4);
347
+ --color-danger-subtle: #FFEBE9;
348
+ --color-done-fg: #8250df;
349
+ --color-done-emphasis: #8250df;
350
+ --color-done-muted: rgba(194,151,255,0.4);
351
+ --color-done-subtle: #fbefff;
352
+ --color-sponsors-fg: #bf3989;
353
+ --color-sponsors-emphasis: #bf3989;
354
+ --color-sponsors-muted: rgba(255,128,200,0.4);
355
+ --color-sponsors-subtle: #ffeff7;
356
+ --color-primer-canvas-backdrop: rgba(27,31,36,0.5);
357
+ --color-primer-canvas-sticky: rgba(255,255,255,0.95);
358
+ --color-primer-border-active: #FD8C73;
359
+ --color-primer-border-contrast: rgba(27,31,36,0.1);
360
+ --color-primer-shadow-highlight: inset 0 1px 0 rgba(255,255,255,0.25);
361
+ --color-primer-shadow-inset: inset 0 1px 0 rgba(208,215,222,0.2);
362
+ --color-primer-shadow-focus: 0 0 0 3px rgba(9,105,218,0.3);
363
+ --color-scale-black: #1b1f24;
364
+ --color-scale-white: #ffffff;
365
+ --color-scale-gray-0: #f6f8fa;
366
+ --color-scale-gray-1: #eaeef2;
367
+ --color-scale-gray-2: #d0d7de;
368
+ --color-scale-gray-3: #afb8c1;
369
+ --color-scale-gray-4: #8c959f;
370
+ --color-scale-gray-5: #6e7781;
371
+ --color-scale-gray-6: #57606a;
372
+ --color-scale-gray-7: #424a53;
373
+ --color-scale-gray-8: #32383f;
374
+ --color-scale-gray-9: #24292f;
375
+ --color-scale-blue-0: #ddf4ff;
376
+ --color-scale-blue-1: #b6e3ff;
377
+ --color-scale-blue-2: #80ccff;
378
+ --color-scale-blue-3: #54aeff;
379
+ --color-scale-blue-4: #218bff;
380
+ --color-scale-blue-5: #0969da;
381
+ --color-scale-blue-6: #0550ae;
382
+ --color-scale-blue-7: #033d8b;
383
+ --color-scale-blue-8: #0a3069;
384
+ --color-scale-blue-9: #002155;
385
+ --color-scale-green-0: #dafbe1;
386
+ --color-scale-green-1: #aceebb;
387
+ --color-scale-green-2: #6fdd8b;
388
+ --color-scale-green-3: #4ac26b;
389
+ --color-scale-green-4: #2da44e;
390
+ --color-scale-green-5: #1a7f37;
391
+ --color-scale-green-6: #116329;
392
+ --color-scale-green-7: #044f1e;
393
+ --color-scale-green-8: #003d16;
394
+ --color-scale-green-9: #002d11;
395
+ --color-scale-yellow-0: #fff8c5;
396
+ --color-scale-yellow-1: #fae17d;
397
+ --color-scale-yellow-2: #eac54f;
398
+ --color-scale-yellow-3: #d4a72c;
399
+ --color-scale-yellow-4: #bf8700;
400
+ --color-scale-yellow-5: #9a6700;
401
+ --color-scale-yellow-6: #7d4e00;
402
+ --color-scale-yellow-7: #633c01;
403
+ --color-scale-yellow-8: #4d2d00;
404
+ --color-scale-yellow-9: #3b2300;
405
+ --color-scale-orange-0: #fff1e5;
406
+ --color-scale-orange-1: #ffd8b5;
407
+ --color-scale-orange-2: #ffb77c;
408
+ --color-scale-orange-3: #fb8f44;
409
+ --color-scale-orange-4: #e16f24;
410
+ --color-scale-orange-5: #bc4c00;
411
+ --color-scale-orange-6: #953800;
412
+ --color-scale-orange-7: #762c00;
413
+ --color-scale-orange-8: #5c2200;
414
+ --color-scale-orange-9: #471700;
415
+ --color-scale-red-0: #FFEBE9;
416
+ --color-scale-red-1: #ffcecb;
417
+ --color-scale-red-2: #ffaba8;
418
+ --color-scale-red-3: #ff8182;
419
+ --color-scale-red-4: #fa4549;
420
+ --color-scale-red-5: #cf222e;
421
+ --color-scale-red-6: #a40e26;
422
+ --color-scale-red-7: #82071e;
423
+ --color-scale-red-8: #660018;
424
+ --color-scale-red-9: #4c0014;
425
+ --color-scale-purple-0: #fbefff;
426
+ --color-scale-purple-1: #ecd8ff;
427
+ --color-scale-purple-2: #d8b9ff;
428
+ --color-scale-purple-3: #c297ff;
429
+ --color-scale-purple-4: #a475f9;
430
+ --color-scale-purple-5: #8250df;
431
+ --color-scale-purple-6: #6639ba;
432
+ --color-scale-purple-7: #512a97;
433
+ --color-scale-purple-8: #3e1f79;
434
+ --color-scale-purple-9: #2e1461;
435
+ --color-scale-pink-0: #ffeff7;
436
+ --color-scale-pink-1: #ffd3eb;
437
+ --color-scale-pink-2: #ffadda;
438
+ --color-scale-pink-3: #ff80c8;
439
+ --color-scale-pink-4: #e85aad;
440
+ --color-scale-pink-5: #bf3989;
441
+ --color-scale-pink-6: #99286e;
442
+ --color-scale-pink-7: #772057;
443
+ --color-scale-pink-8: #611347;
444
+ --color-scale-pink-9: #4d0336;
445
+ --color-scale-coral-0: #FFF0EB;
446
+ --color-scale-coral-1: #FFD6CC;
447
+ --color-scale-coral-2: #FFB4A1;
448
+ --color-scale-coral-3: #FD8C73;
449
+ --color-scale-coral-4: #EC6547;
450
+ --color-scale-coral-5: #C4432B;
451
+ --color-scale-coral-6: #9E2F1C;
452
+ --color-scale-coral-7: #801F0F;
453
+ --color-scale-coral-8: #691105;
454
+ --color-scale-coral-9: #510901
455
+ }
456
+
457
+ @media(prefers-color-scheme: dark) {
458
+ :root {
459
+ --color-canvas-default-transparent: rgba(13,17,23,0);
460
+ --color-marketing-icon-primary: #79c0ff;
461
+ --color-marketing-icon-secondary: #1f6feb;
462
+ --color-diff-blob-addition-num-text: #c9d1d9;
463
+ --color-diff-blob-addition-fg: #c9d1d9;
464
+ --color-diff-blob-addition-num-bg: rgba(63,185,80,0.3);
465
+ --color-diff-blob-addition-line-bg: rgba(46,160,67,0.15);
466
+ --color-diff-blob-addition-word-bg: rgba(46,160,67,0.4);
467
+ --color-diff-blob-deletion-num-text: #c9d1d9;
468
+ --color-diff-blob-deletion-fg: #c9d1d9;
469
+ --color-diff-blob-deletion-num-bg: rgba(248,81,73,0.3);
470
+ --color-diff-blob-deletion-line-bg: rgba(248,81,73,0.15);
471
+ --color-diff-blob-deletion-word-bg: rgba(248,81,73,0.4);
472
+ --color-diff-blob-hunk-num-bg: rgba(56,139,253,0.4);
473
+ --color-diff-blob-expander-icon: #8b949e;
474
+ --color-diff-blob-selected-line-highlight-mix-blend-mode: screen;
475
+ --color-diffstat-deletion-border: rgba(240,246,252,0.1);
476
+ --color-diffstat-addition-border: rgba(240,246,252,0.1);
477
+ --color-diffstat-addition-bg: #3fb950;
478
+ --color-search-keyword-hl: rgba(210,153,34,0.4);
479
+ --color-prettylights-syntax-comment: #8b949e;
480
+ --color-prettylights-syntax-constant: #79c0ff;
481
+ --color-prettylights-syntax-entity: #d2a8ff;
482
+ --color-prettylights-syntax-storage-modifier-import: #c9d1d9;
483
+ --color-prettylights-syntax-entity-tag: #7ee787;
484
+ --color-prettylights-syntax-keyword: #ff7b72;
485
+ --color-prettylights-syntax-string: #a5d6ff;
486
+ --color-prettylights-syntax-variable: #ffa657;
487
+ --color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
488
+ --color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
489
+ --color-prettylights-syntax-invalid-illegal-bg: #8e1519;
490
+ --color-prettylights-syntax-carriage-return-text: #f0f6fc;
491
+ --color-prettylights-syntax-carriage-return-bg: #b62324;
492
+ --color-prettylights-syntax-string-regexp: #7ee787;
493
+ --color-prettylights-syntax-markup-list: #f2cc60;
494
+ --color-prettylights-syntax-markup-heading: #1f6feb;
495
+ --color-prettylights-syntax-markup-italic: #c9d1d9;
496
+ --color-prettylights-syntax-markup-bold: #c9d1d9;
497
+ --color-prettylights-syntax-markup-deleted-text: #ffdcd7;
498
+ --color-prettylights-syntax-markup-deleted-bg: #67060c;
499
+ --color-prettylights-syntax-markup-inserted-text: #aff5b4;
500
+ --color-prettylights-syntax-markup-inserted-bg: #033a16;
501
+ --color-prettylights-syntax-markup-changed-text: #ffdfb6;
502
+ --color-prettylights-syntax-markup-changed-bg: #5a1e02;
503
+ --color-prettylights-syntax-markup-ignored-text: #c9d1d9;
504
+ --color-prettylights-syntax-markup-ignored-bg: #1158c7;
505
+ --color-prettylights-syntax-meta-diff-range: #d2a8ff;
506
+ --color-prettylights-syntax-brackethighlighter-angle: #8b949e;
507
+ --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
508
+ --color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
509
+ --color-codemirror-text: #c9d1d9;
510
+ --color-codemirror-bg: #0d1117;
511
+ --color-codemirror-gutters-bg: #0d1117;
512
+ --color-codemirror-guttermarker-text: #0d1117;
513
+ --color-codemirror-guttermarker-subtle-text: #484f58;
514
+ --color-codemirror-linenumber-text: #8b949e;
515
+ --color-codemirror-cursor: #c9d1d9;
516
+ --color-codemirror-selection-bg: rgba(56,139,253,0.4);
517
+ --color-codemirror-activeline-bg: rgba(110,118,129,0.1);
518
+ --color-codemirror-matchingbracket-text: #c9d1d9;
519
+ --color-codemirror-lines-bg: #0d1117;
520
+ --color-codemirror-syntax-comment: #8b949e;
521
+ --color-codemirror-syntax-constant: #79c0ff;
522
+ --color-codemirror-syntax-entity: #d2a8ff;
523
+ --color-codemirror-syntax-keyword: #ff7b72;
524
+ --color-codemirror-syntax-storage: #ff7b72;
525
+ --color-codemirror-syntax-string: #a5d6ff;
526
+ --color-codemirror-syntax-support: #79c0ff;
527
+ --color-codemirror-syntax-variable: #ffa657;
528
+ --color-checks-bg: #010409;
529
+ --color-checks-run-border-width: 1px;
530
+ --color-checks-container-border-width: 1px;
531
+ --color-checks-text-primary: #c9d1d9;
532
+ --color-checks-text-secondary: #8b949e;
533
+ --color-checks-text-link: #58a6ff;
534
+ --color-checks-btn-icon: #8b949e;
535
+ --color-checks-btn-hover-icon: #c9d1d9;
536
+ --color-checks-btn-hover-bg: rgba(110,118,129,0.1);
537
+ --color-checks-input-text: #8b949e;
538
+ --color-checks-input-placeholder-text: #484f58;
539
+ --color-checks-input-focus-text: #c9d1d9;
540
+ --color-checks-input-bg: #161b22;
541
+ --color-checks-input-shadow: none;
542
+ --color-checks-donut-error: #f85149;
543
+ --color-checks-donut-pending: #d29922;
544
+ --color-checks-donut-success: #2ea043;
545
+ --color-checks-donut-neutral: #8b949e;
546
+ --color-checks-dropdown-text: #c9d1d9;
547
+ --color-checks-dropdown-bg: #161b22;
548
+ --color-checks-dropdown-border: #30363d;
549
+ --color-checks-dropdown-shadow: rgba(1,4,9,0.3);
550
+ --color-checks-dropdown-hover-text: #c9d1d9;
551
+ --color-checks-dropdown-hover-bg: rgba(110,118,129,0.1);
552
+ --color-checks-dropdown-btn-hover-text: #c9d1d9;
553
+ --color-checks-dropdown-btn-hover-bg: rgba(110,118,129,0.1);
554
+ --color-checks-scrollbar-thumb-bg: rgba(110,118,129,0.4);
555
+ --color-checks-header-label-text: #8b949e;
556
+ --color-checks-header-label-open-text: #c9d1d9;
557
+ --color-checks-header-border: #21262d;
558
+ --color-checks-header-icon: #8b949e;
559
+ --color-checks-line-text: #8b949e;
560
+ --color-checks-line-num-text: #484f58;
561
+ --color-checks-line-timestamp-text: #484f58;
562
+ --color-checks-line-hover-bg: rgba(110,118,129,0.1);
563
+ --color-checks-line-selected-bg: rgba(56,139,253,0.15);
564
+ --color-checks-line-selected-num-text: #58a6ff;
565
+ --color-checks-line-dt-fm-text: #f0f6fc;
566
+ --color-checks-line-dt-fm-bg: #9e6a03;
567
+ --color-checks-gate-bg: rgba(187,128,9,0.15);
568
+ --color-checks-gate-text: #8b949e;
569
+ --color-checks-gate-waiting-text: #d29922;
570
+ --color-checks-step-header-open-bg: #161b22;
571
+ --color-checks-step-error-text: #f85149;
572
+ --color-checks-step-warning-text: #d29922;
573
+ --color-checks-logline-text: #8b949e;
574
+ --color-checks-logline-num-text: #484f58;
575
+ --color-checks-logline-debug-text: #a371f7;
576
+ --color-checks-logline-error-text: #8b949e;
577
+ --color-checks-logline-error-num-text: #484f58;
578
+ --color-checks-logline-error-bg: rgba(248,81,73,0.15);
579
+ --color-checks-logline-warning-text: #8b949e;
580
+ --color-checks-logline-warning-num-text: #d29922;
581
+ --color-checks-logline-warning-bg: rgba(187,128,9,0.15);
582
+ --color-checks-logline-command-text: #58a6ff;
583
+ --color-checks-logline-section-text: #3fb950;
584
+ --color-checks-ansi-black: #0d1117;
585
+ --color-checks-ansi-black-bright: #161b22;
586
+ --color-checks-ansi-white: #b1bac4;
587
+ --color-checks-ansi-white-bright: #b1bac4;
588
+ --color-checks-ansi-gray: #6e7681;
589
+ --color-checks-ansi-red: #ff7b72;
590
+ --color-checks-ansi-red-bright: #ffa198;
591
+ --color-checks-ansi-green: #3fb950;
592
+ --color-checks-ansi-green-bright: #56d364;
593
+ --color-checks-ansi-yellow: #d29922;
594
+ --color-checks-ansi-yellow-bright: #e3b341;
595
+ --color-checks-ansi-blue: #58a6ff;
596
+ --color-checks-ansi-blue-bright: #79c0ff;
597
+ --color-checks-ansi-magenta: #bc8cff;
598
+ --color-checks-ansi-magenta-bright: #d2a8ff;
599
+ --color-checks-ansi-cyan: #76e3ea;
600
+ --color-checks-ansi-cyan-bright: #b3f0ff;
601
+ --color-project-header-bg: #0d1117;
602
+ --color-project-sidebar-bg: #161b22;
603
+ --color-project-gradient-in: #161b22;
604
+ --color-project-gradient-out: rgba(22,27,34,0);
605
+ --color-mktg-success: rgba(41,147,61,1);
606
+ --color-mktg-info: rgba(42,123,243,1);
607
+ --color-mktg-bg-shade-gradient-top: rgba(1,4,9,0.065);
608
+ --color-mktg-bg-shade-gradient-bottom: rgba(1,4,9,0);
609
+ --color-mktg-btn-bg-top: hsla(228,82%,66%,1);
610
+ --color-mktg-btn-bg-bottom: #4969ed;
611
+ --color-mktg-btn-bg-overlay-top: hsla(228,74%,59%,1);
612
+ --color-mktg-btn-bg-overlay-bottom: #3355e0;
613
+ --color-mktg-btn-text: #f0f6fc;
614
+ --color-mktg-btn-primary-bg-top: hsla(137,56%,46%,1);
615
+ --color-mktg-btn-primary-bg-bottom: #2ea44f;
616
+ --color-mktg-btn-primary-bg-overlay-top: hsla(134,60%,38%,1);
617
+ --color-mktg-btn-primary-bg-overlay-bottom: #22863a;
618
+ --color-mktg-btn-primary-text: #f0f6fc;
619
+ --color-mktg-btn-enterprise-bg-top: hsla(249,100%,72%,1);
620
+ --color-mktg-btn-enterprise-bg-bottom: #6f57ff;
621
+ --color-mktg-btn-enterprise-bg-overlay-top: hsla(248,65%,63%,1);
622
+ --color-mktg-btn-enterprise-bg-overlay-bottom: #614eda;
623
+ --color-mktg-btn-enterprise-text: #f0f6fc;
624
+ --color-mktg-btn-outline-text: #f0f6fc;
625
+ --color-mktg-btn-outline-border: rgba(240,246,252,0.3);
626
+ --color-mktg-btn-outline-hover-text: #f0f6fc;
627
+ --color-mktg-btn-outline-hover-border: rgba(240,246,252,0.5);
628
+ --color-mktg-btn-outline-focus-border: #f0f6fc;
629
+ --color-mktg-btn-outline-focus-border-inset: rgba(240,246,252,0.5);
630
+ --color-mktg-btn-dark-text: #f0f6fc;
631
+ --color-mktg-btn-dark-border: rgba(240,246,252,0.3);
632
+ --color-mktg-btn-dark-hover-text: #f0f6fc;
633
+ --color-mktg-btn-dark-hover-border: rgba(240,246,252,0.5);
634
+ --color-mktg-btn-dark-focus-border: #f0f6fc;
635
+ --color-mktg-btn-dark-focus-border-inset: rgba(240,246,252,0.5);
636
+ --color-avatar-bg: rgba(240,246,252,0.1);
637
+ --color-avatar-border: rgba(240,246,252,0.1);
638
+ --color-avatar-stack-fade: #30363d;
639
+ --color-avatar-stack-fade-more: #21262d;
640
+ --color-avatar-child-shadow: -2px -2px 0 #0d1117;
641
+ --color-topic-tag-border: rgba(0,0,0,0);
642
+ --color-select-menu-backdrop-border: #484f58;
643
+ --color-select-menu-tap-highlight: rgba(48,54,61,0.5);
644
+ --color-select-menu-tap-focus-bg: #0c2d6b;
645
+ --color-overlay-shadow: 0 0 0 1px #30363d, 0 16px 32px rgba(1,4,9,0.85);
646
+ --color-header-text: rgba(240,246,252,0.7);
647
+ --color-header-bg: #161b22;
648
+ --color-header-logo: #f0f6fc;
649
+ --color-header-search-bg: #0d1117;
650
+ --color-header-search-border: #30363d;
651
+ --color-sidenav-selected-bg: #21262d;
652
+ --color-menu-bg-active: #161b22;
653
+ --color-control-transparent-bg-hover: #656c7633;
654
+ --color-input-disabled-bg: rgba(110,118,129,0);
655
+ --color-timeline-badge-bg: #21262d;
656
+ --color-ansi-black: #484f58;
657
+ --color-ansi-black-bright: #6e7681;
658
+ --color-ansi-white: #b1bac4;
659
+ --color-ansi-white-bright: #f0f6fc;
660
+ --color-ansi-gray: #6e7681;
661
+ --color-ansi-red: #ff7b72;
662
+ --color-ansi-red-bright: #ffa198;
663
+ --color-ansi-green: #3fb950;
664
+ --color-ansi-green-bright: #56d364;
665
+ --color-ansi-yellow: #d29922;
666
+ --color-ansi-yellow-bright: #e3b341;
667
+ --color-ansi-blue: #58a6ff;
668
+ --color-ansi-blue-bright: #79c0ff;
669
+ --color-ansi-magenta: #bc8cff;
670
+ --color-ansi-magenta-bright: #d2a8ff;
671
+ --color-ansi-cyan: #39c5cf;
672
+ --color-ansi-cyan-bright: #56d4dd;
673
+ --color-btn-text: #c9d1d9;
674
+ --color-btn-bg: #21262d;
675
+ --color-btn-border: rgba(240,246,252,0.1);
676
+ --color-btn-shadow: 0 0 transparent;
677
+ --color-btn-inset-shadow: 0 0 transparent;
678
+ --color-btn-hover-bg: #30363d;
679
+ --color-btn-hover-border: #8b949e;
680
+ --color-btn-active-bg: hsla(212,12%,18%,1);
681
+ --color-btn-active-border: #6e7681;
682
+ --color-btn-selected-bg: #161b22;
683
+ --color-btn-focus-bg: #21262d;
684
+ --color-btn-focus-border: #8b949e;
685
+ --color-btn-focus-shadow: 0 0 0 3px rgba(139,148,158,0.3);
686
+ --color-btn-shadow-active: inset 0 0.15em 0.3em rgba(1,4,9,0.15);
687
+ --color-btn-shadow-input-focus: 0 0 0 0.2em rgba(31,111,235,0.3);
688
+ --color-btn-counter-bg: #30363d;
689
+ --color-btn-primary-text: #ffffff;
690
+ --color-btn-primary-bg: #238636;
691
+ --color-btn-primary-border: rgba(240,246,252,0.1);
692
+ --color-btn-primary-shadow: 0 0 transparent;
693
+ --color-btn-primary-inset-shadow: 0 0 transparent;
694
+ --color-btn-primary-hover-bg: #2ea043;
695
+ --color-btn-primary-hover-border: rgba(240,246,252,0.1);
696
+ --color-btn-primary-selected-bg: #238636;
697
+ --color-btn-primary-selected-shadow: 0 0 transparent;
698
+ --color-btn-primary-disabled-text: rgba(240,246,252,0.5);
699
+ --color-btn-primary-disabled-bg: rgba(35,134,54,0.6);
700
+ --color-btn-primary-disabled-border: rgba(240,246,252,0.1);
701
+ --color-btn-primary-focus-bg: #238636;
702
+ --color-btn-primary-focus-border: rgba(240,246,252,0.1);
703
+ --color-btn-primary-focus-shadow: 0 0 0 3px rgba(46,164,79,0.4);
704
+ --color-btn-primary-icon: #f0f6fc;
705
+ --color-btn-primary-counter-bg: rgba(240,246,252,0.2);
706
+ --color-btn-outline-text: #58a6ff;
707
+ --color-btn-outline-hover-text: #58a6ff;
708
+ --color-btn-outline-hover-bg: #30363d;
709
+ --color-btn-outline-hover-border: rgba(240,246,252,0.1);
710
+ --color-btn-outline-hover-shadow: 0 1px 0 rgba(1,4,9,0.1);
711
+ --color-btn-outline-hover-inset-shadow: inset 0 1px 0 rgba(240,246,252,0.03);
712
+ --color-btn-outline-hover-counter-bg: rgba(240,246,252,0.2);
713
+ --color-btn-outline-selected-text: #f0f6fc;
714
+ --color-btn-outline-selected-bg: #0d419d;
715
+ --color-btn-outline-selected-border: rgba(240,246,252,0.1);
716
+ --color-btn-outline-selected-shadow: 0 0 transparent;
717
+ --color-btn-outline-disabled-text: rgba(88,166,255,0.5);
718
+ --color-btn-outline-disabled-bg: #0d1117;
719
+ --color-btn-outline-disabled-counter-bg: rgba(31,111,235,0.05);
720
+ --color-btn-outline-focus-border: rgba(240,246,252,0.1);
721
+ --color-btn-outline-focus-shadow: 0 0 0 3px rgba(17,88,199,0.4);
722
+ --color-btn-outline-counter-bg: rgba(31,111,235,0.1);
723
+ --color-btn-danger-text: #f85149;
724
+ --color-btn-danger-hover-text: #f0f6fc;
725
+ --color-btn-danger-hover-bg: #da3633;
726
+ --color-btn-danger-hover-border: #f85149;
727
+ --color-btn-danger-hover-shadow: 0 0 transparent;
728
+ --color-btn-danger-hover-inset-shadow: 0 0 transparent;
729
+ --color-btn-danger-hover-icon: #f0f6fc;
730
+ --color-btn-danger-hover-counter-bg: rgba(255,255,255,0.2);
731
+ --color-btn-danger-selected-text: #ffffff;
732
+ --color-btn-danger-selected-bg: #b62324;
733
+ --color-btn-danger-selected-border: #ff7b72;
734
+ --color-btn-danger-selected-shadow: 0 0 transparent;
735
+ --color-btn-danger-disabled-text: rgba(248,81,73,0.5);
736
+ --color-btn-danger-disabled-bg: #0d1117;
737
+ --color-btn-danger-disabled-counter-bg: rgba(218,54,51,0.05);
738
+ --color-btn-danger-focus-border: #f85149;
739
+ --color-btn-danger-focus-shadow: 0 0 0 3px rgba(248,81,73,0.4);
740
+ --color-btn-danger-counter-bg: rgba(218,54,51,0.1);
741
+ --color-btn-danger-icon: #f85149;
742
+ --color-underlinenav-icon: #484f58;
743
+ --color-underlinenav-border-hover: rgba(110,118,129,0.4);
744
+ --color-fg-default: #c9d1d9;
745
+ --color-fg-muted: #8b949e;
746
+ --color-fg-subtle: #484f58;
747
+ --color-fg-on-emphasis: #f0f6fc;
748
+ --color-canvas-default: #0d1117;
749
+ --color-canvas-overlay: #161b22;
750
+ --color-canvas-inset: #010409;
751
+ --color-canvas-subtle: #161b22;
752
+ --color-border-default: #30363d;
753
+ --color-border-muted: #21262d;
754
+ --color-border-subtle: rgba(240,246,252,0.1);
755
+ --color-shadow-small: 0 0 transparent;
756
+ --color-shadow-medium: 0 3px 6px #010409;
757
+ --color-shadow-large: 0 8px 24px #010409;
758
+ --color-shadow-extra-large: 0 12px 48px #010409;
759
+ --color-neutral-emphasis-plus: #6e7681;
760
+ --color-neutral-emphasis: #6e7681;
761
+ --color-neutral-muted: rgba(110,118,129,0.4);
762
+ --color-neutral-subtle: rgba(110,118,129,0.1);
763
+ --color-accent-fg: #58a6ff;
764
+ --color-accent-emphasis: #1f6feb;
765
+ --color-accent-muted: rgba(56,139,253,0.4);
766
+ --color-accent-subtle: rgba(56,139,253,0.15);
767
+ --color-success-fg: #3fb950;
768
+ --color-success-emphasis: #238636;
769
+ --color-success-muted: rgba(46,160,67,0.4);
770
+ --color-success-subtle: rgba(46,160,67,0.15);
771
+ --color-attention-fg: #d29922;
772
+ --color-attention-emphasis: #9e6a03;
773
+ --color-attention-muted: rgba(187,128,9,0.4);
774
+ --color-attention-subtle: rgba(187,128,9,0.15);
775
+ --color-severe-fg: #db6d28;
776
+ --color-severe-emphasis: #bd561d;
777
+ --color-severe-muted: rgba(219,109,40,0.4);
778
+ --color-severe-subtle: rgba(219,109,40,0.15);
779
+ --color-danger-fg: #f85149;
780
+ --color-danger-emphasis: #da3633;
781
+ --color-danger-muted: rgba(248,81,73,0.4);
782
+ --color-danger-subtle: rgba(248,81,73,0.15);
783
+ --color-done-fg: #a371f7;
784
+ --color-done-emphasis: #8957e5;
785
+ --color-done-muted: rgba(163,113,247,0.4);
786
+ --color-done-subtle: rgba(163,113,247,0.15);
787
+ --color-sponsors-fg: #db61a2;
788
+ --color-sponsors-emphasis: #bf4b8a;
789
+ --color-sponsors-muted: rgba(219,97,162,0.4);
790
+ --color-sponsors-subtle: rgba(219,97,162,0.15);
791
+ --color-primer-canvas-backdrop: rgba(1,4,9,0.8);
792
+ --color-primer-canvas-sticky: rgba(13,17,23,0.95);
793
+ --color-primer-border-active: #F78166;
794
+ --color-primer-border-contrast: rgba(240,246,252,0.2);
795
+ --color-primer-shadow-highlight: 0 0 transparent;
796
+ --color-primer-shadow-inset: 0 0 transparent;
797
+ --color-primer-shadow-focus: 0 0 0 3px #0c2d6b;
798
+ --color-scale-black: #010409;
799
+ --color-scale-white: #f0f6fc;
800
+ --color-scale-gray-0: #f0f6fc;
801
+ --color-scale-gray-1: #c9d1d9;
802
+ --color-scale-gray-2: #b1bac4;
803
+ --color-scale-gray-3: #8b949e;
804
+ --color-scale-gray-4: #6e7681;
805
+ --color-scale-gray-5: #484f58;
806
+ --color-scale-gray-6: #30363d;
807
+ --color-scale-gray-7: #21262d;
808
+ --color-scale-gray-8: #161b22;
809
+ --color-scale-gray-9: #0d1117;
810
+ --color-scale-blue-0: #cae8ff;
811
+ --color-scale-blue-1: #a5d6ff;
812
+ --color-scale-blue-2: #79c0ff;
813
+ --color-scale-blue-3: #58a6ff;
814
+ --color-scale-blue-4: #388bfd;
815
+ --color-scale-blue-5: #1f6feb;
816
+ --color-scale-blue-6: #1158c7;
817
+ --color-scale-blue-7: #0d419d;
818
+ --color-scale-blue-8: #0c2d6b;
819
+ --color-scale-blue-9: #051d4d;
820
+ --color-scale-green-0: #aff5b4;
821
+ --color-scale-green-1: #7ee787;
822
+ --color-scale-green-2: #56d364;
823
+ --color-scale-green-3: #3fb950;
824
+ --color-scale-green-4: #2ea043;
825
+ --color-scale-green-5: #238636;
826
+ --color-scale-green-6: #196c2e;
827
+ --color-scale-green-7: #0f5323;
828
+ --color-scale-green-8: #033a16;
829
+ --color-scale-green-9: #04260f;
830
+ --color-scale-yellow-0: #f8e3a1;
831
+ --color-scale-yellow-1: #f2cc60;
832
+ --color-scale-yellow-2: #e3b341;
833
+ --color-scale-yellow-3: #d29922;
834
+ --color-scale-yellow-4: #bb8009;
835
+ --color-scale-yellow-5: #9e6a03;
836
+ --color-scale-yellow-6: #845306;
837
+ --color-scale-yellow-7: #693e00;
838
+ --color-scale-yellow-8: #4b2900;
839
+ --color-scale-yellow-9: #341a00;
840
+ --color-scale-orange-0: #ffdfb6;
841
+ --color-scale-orange-1: #ffc680;
842
+ --color-scale-orange-2: #ffa657;
843
+ --color-scale-orange-3: #f0883e;
844
+ --color-scale-orange-4: #db6d28;
845
+ --color-scale-orange-5: #bd561d;
846
+ --color-scale-orange-6: #9b4215;
847
+ --color-scale-orange-7: #762d0a;
848
+ --color-scale-orange-8: #5a1e02;
849
+ --color-scale-orange-9: #3d1300;
850
+ --color-scale-red-0: #ffdcd7;
851
+ --color-scale-red-1: #ffc1ba;
852
+ --color-scale-red-2: #ffa198;
853
+ --color-scale-red-3: #ff7b72;
854
+ --color-scale-red-4: #f85149;
855
+ --color-scale-red-5: #da3633;
856
+ --color-scale-red-6: #b62324;
857
+ --color-scale-red-7: #8e1519;
858
+ --color-scale-red-8: #67060c;
859
+ --color-scale-red-9: #490202;
860
+ --color-scale-purple-0: #eddeff;
861
+ --color-scale-purple-1: #e2c5ff;
862
+ --color-scale-purple-2: #d2a8ff;
863
+ --color-scale-purple-3: #bc8cff;
864
+ --color-scale-purple-4: #a371f7;
865
+ --color-scale-purple-5: #8957e5;
866
+ --color-scale-purple-6: #6e40c9;
867
+ --color-scale-purple-7: #553098;
868
+ --color-scale-purple-8: #3c1e70;
869
+ --color-scale-purple-9: #271052;
870
+ --color-scale-pink-0: #ffdaec;
871
+ --color-scale-pink-1: #ffbedd;
872
+ --color-scale-pink-2: #ff9bce;
873
+ --color-scale-pink-3: #f778ba;
874
+ --color-scale-pink-4: #db61a2;
875
+ --color-scale-pink-5: #bf4b8a;
876
+ --color-scale-pink-6: #9e3670;
877
+ --color-scale-pink-7: #7d2457;
878
+ --color-scale-pink-8: #5e103e;
879
+ --color-scale-pink-9: #42062a;
880
+ --color-scale-coral-0: #FFDDD2;
881
+ --color-scale-coral-1: #FFC2B2;
882
+ --color-scale-coral-2: #FFA28B;
883
+ --color-scale-coral-3: #F78166;
884
+ --color-scale-coral-4: #EA6045;
885
+ --color-scale-coral-5: #CF462D;
886
+ --color-scale-coral-6: #AC3220;
887
+ --color-scale-coral-7: #872012;
888
+ --color-scale-coral-8: #640D04;
889
+ --color-scale-coral-9: #460701
890
+ }
891
+ }
extension/src/ui/connect.css CHANGED
@@ -203,4 +203,60 @@ body {
203
  cursor: pointer;
204
  padding: 0;
205
  font: inherit;
206
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  cursor: pointer;
204
  padding: 0;
205
  font: inherit;
206
+ }
207
+
208
+ /* Auth token section */
209
+ .auth-token-section {
210
+ margin: 16px 0;
211
+ padding: 16px;
212
+ background-color: #f6f8fa;
213
+ border-radius: 6px;
214
+ }
215
+
216
+ .auth-token-description {
217
+ font-size: 12px;
218
+ color: #656d76;
219
+ margin-bottom: 12px;
220
+ }
221
+
222
+ .auth-token-container {
223
+ display: flex;
224
+ align-items: center;
225
+ gap: 8px;
226
+ background-color: #ffffff;
227
+ padding: 8px;
228
+ }
229
+
230
+ .auth-token-code {
231
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
232
+ font-size: 12px;
233
+ color: #1f2328;
234
+ border: none;
235
+ flex: 1;
236
+ padding: 0;
237
+ word-break: break-all;
238
+ }
239
+
240
+ .auth-token-refresh {
241
+ flex: none;
242
+ height: 24px;
243
+ width: 24px;
244
+ border: none;
245
+ outline: none;
246
+ color: var(--color-fg-muted);
247
+ background: transparent;
248
+ padding: 4px;
249
+ cursor: pointer;
250
+ display: inline-flex;
251
+ align-items: center;
252
+ justify-content: center;
253
+ border-radius: 4px;
254
+ }
255
+
256
+ .auth-token-refresh svg {
257
+ margin: 0;
258
+ }
259
+
260
+ .auth-token-refresh:not(:disabled):hover {
261
+ background-color: var(--color-btn-selected-bg);
262
+ }
extension/src/ui/connect.tsx CHANGED
@@ -14,9 +14,11 @@
14
  * limitations under the License.
15
  */
16
 
17
- import React, { useState, useEffect, useCallback } from 'react';
18
  import { createRoot } from 'react-dom/client';
19
- import { Button, TabItem } from './tabItem';
 
 
20
  import type { TabInfo } from './tabItem';
21
 
22
  type Status =
@@ -37,54 +39,69 @@ const ConnectApp: React.FC = () => {
37
  const [newTab, setNewTab] = useState<boolean>(false);
38
 
39
  useEffect(() => {
40
- const params = new URLSearchParams(window.location.search);
41
- const relayUrl = params.get('mcpRelayUrl');
 
 
 
 
 
 
 
42
 
43
- if (!relayUrl) {
44
- setShowButtons(false);
45
- setStatus({ type: 'error', message: 'Missing mcpRelayUrl parameter in URL.' });
46
- return;
47
- }
48
 
49
- setMcpRelayUrl(relayUrl);
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- try {
52
- const client = JSON.parse(params.get('client') || '{}');
53
- const info = `${client.name}/${client.version}`;
54
- setClientInfo(info);
55
- setStatus({
56
- type: 'connecting',
57
- message: `🎭 Playwright MCP started from "${info}" is trying to connect. Do you want to continue?`
58
- });
59
- } catch (e) {
60
- setStatus({ type: 'error', message: 'Failed to parse client version.' });
61
- return;
62
- }
 
 
63
 
64
- const parsedVersion = parseInt(params.get('protocolVersion') ?? '', 10);
65
- const requiredVersion = isNaN(parsedVersion) ? 1 : parsedVersion;
66
- if (requiredVersion > SUPPORTED_PROTOCOL_VERSION) {
67
- const extensionVersion = chrome.runtime.getManifest().version;
68
- setShowButtons(false);
69
- setShowTabList(false);
70
- setStatus({
71
- type: 'error',
72
- versionMismatch: {
73
- extensionVersion,
74
- }
75
- });
76
- return;
77
- }
78
 
79
- void connectToMCPRelay(relayUrl);
80
 
81
- // If this is a browser_navigate command, hide the tab list and show simple allow/reject
82
- if (params.get('newTab') === 'true') {
83
- setNewTab(true);
84
- setShowTabList(false);
85
- } else {
86
- void loadTabs();
87
- }
 
 
88
  }, []);
89
 
90
  const handleReject = useCallback((message: string) => {
@@ -94,7 +111,6 @@ const ConnectApp: React.FC = () => {
94
  }, []);
95
 
96
  const connectToMCPRelay = useCallback(async (mcpRelayUrl: string) => {
97
-
98
  const response = await chrome.runtime.sendMessage({ type: 'connectToMCPRelay', mcpRelayUrl });
99
  if (!response.success)
100
  handleReject(response.error);
@@ -174,6 +190,10 @@ const ConnectApp: React.FC = () => {
174
  </div>
175
  )}
176
 
 
 
 
 
177
  {showTabList && (
178
  <div>
179
  <div className='tab-section-title'>
 
14
  * limitations under the License.
15
  */
16
 
17
+ import React, { useCallback, useEffect, useState } from 'react';
18
  import { createRoot } from 'react-dom/client';
19
+ import { Button, TabItem } from './tabItem';
20
+ import { AuthTokenSection, getOrCreateAuthToken } from './authToken';
21
+
22
  import type { TabInfo } from './tabItem';
23
 
24
  type Status =
 
39
  const [newTab, setNewTab] = useState<boolean>(false);
40
 
41
  useEffect(() => {
42
+ const runAsync = async () => {
43
+ const params = new URLSearchParams(window.location.search);
44
+ const relayUrl = params.get('mcpRelayUrl');
45
+
46
+ if (!relayUrl) {
47
+ setShowButtons(false);
48
+ setStatus({ type: 'error', message: 'Missing mcpRelayUrl parameter in URL.' });
49
+ return;
50
+ }
51
 
52
+ setMcpRelayUrl(relayUrl);
 
 
 
 
53
 
54
+ try {
55
+ const client = JSON.parse(params.get('client') || '{}');
56
+ const info = `${client.name}/${client.version}`;
57
+ setClientInfo(info);
58
+ setStatus({
59
+ type: 'connecting',
60
+ message: `🎭 Playwright MCP started from "${info}" is trying to connect. Do you want to continue?`
61
+ });
62
+ } catch (e) {
63
+ setStatus({ type: 'error', message: 'Failed to parse client version.' });
64
+ return;
65
+ }
66
 
67
+ const parsedVersion = parseInt(params.get('protocolVersion') ?? '', 10);
68
+ const requiredVersion = isNaN(parsedVersion) ? 1 : parsedVersion;
69
+ if (requiredVersion > SUPPORTED_PROTOCOL_VERSION) {
70
+ const extensionVersion = chrome.runtime.getManifest().version;
71
+ setShowButtons(false);
72
+ setShowTabList(false);
73
+ setStatus({
74
+ type: 'error',
75
+ versionMismatch: {
76
+ extensionVersion,
77
+ }
78
+ });
79
+ return;
80
+ }
81
 
82
+ const expectedToken = getOrCreateAuthToken();
83
+ const token = params.get('token');
84
+ if (token === expectedToken) {
85
+ await connectToMCPRelay(relayUrl);
86
+ await handleConnectToTab();
87
+ return;
88
+ }
89
+ if (token) {
90
+ handleReject('Invalid token provided.');
91
+ return;
92
+ }
 
 
 
93
 
94
+ await connectToMCPRelay(relayUrl);
95
 
96
+ // If this is a browser_navigate command, hide the tab list and show simple allow/reject
97
+ if (params.get('newTab') === 'true') {
98
+ setNewTab(true);
99
+ setShowTabList(false);
100
+ } else {
101
+ await loadTabs();
102
+ }
103
+ };
104
+ void runAsync();
105
  }, []);
106
 
107
  const handleReject = useCallback((message: string) => {
 
111
  }, []);
112
 
113
  const connectToMCPRelay = useCallback(async (mcpRelayUrl: string) => {
 
114
  const response = await chrome.runtime.sendMessage({ type: 'connectToMCPRelay', mcpRelayUrl });
115
  if (!response.success)
116
  handleReject(response.error);
 
190
  </div>
191
  )}
192
 
193
+ {status?.type === 'connecting' && (
194
+ <AuthTokenSection />
195
+ )}
196
+
197
  {showTabList && (
198
  <div>
199
  <div className='tab-section-title'>
extension/src/ui/copyToClipboard.css ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ Copyright (c) Microsoft Corporation.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ .copy-icon {
18
+ flex: none;
19
+ height: 24px;
20
+ width: 24px;
21
+ border: none;
22
+ outline: none;
23
+ color: var(--color-fg-muted);
24
+ background: transparent;
25
+ padding: 4px;
26
+ cursor: pointer;
27
+ display: inline-flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ border-radius: 4px;
31
+ }
32
+
33
+ .copy-icon svg {
34
+ margin: 0;
35
+ }
36
+
37
+ .copy-icon:not(:disabled):hover {
38
+ background-color: var(--color-btn-selected-bg);
39
+ }
extension/src/ui/copyToClipboard.tsx ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import * as React from 'react';
18
+ import * as icons from './icons';
19
+ import './copyToClipboard.css';
20
+
21
+ type CopyToClipboardProps = {
22
+ value: string;
23
+ };
24
+
25
+ /**
26
+ * A copy to clipboard button.
27
+ */
28
+ export const CopyToClipboard: React.FunctionComponent<CopyToClipboardProps> = ({ value }) => {
29
+ type IconType = 'copy' | 'check' | 'cross';
30
+ const [icon, setIcon] = React.useState<IconType>('copy');
31
+
32
+ React.useEffect(() => {
33
+ setIcon('copy');
34
+ }, [value]);
35
+
36
+ React.useEffect(() => {
37
+ if (icon === 'check') {
38
+ const timeout = setTimeout(() => {
39
+ setIcon('copy');
40
+ }, 3000);
41
+ return () => clearTimeout(timeout);
42
+ }
43
+ }, [icon]);
44
+
45
+ const handleCopy = React.useCallback(() => {
46
+ navigator.clipboard.writeText(value).then(() => {
47
+ setIcon('check');
48
+ }, () => {
49
+ setIcon('cross');
50
+ });
51
+ }, [value]);
52
+ const iconElement = icon === 'check' ? icons.check() : icon === 'cross' ? icons.cross() : icons.copy();
53
+ return <button className='copy-icon' title='Copy to clipboard' aria-label='Copy to clipboard' onClick={handleCopy}>{iconElement}</button>;
54
+ };
extension/src/ui/icons.css ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ Copyright (c) Microsoft Corporation.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ .octicon {
18
+ display: inline-block;
19
+ overflow: visible !important;
20
+ vertical-align: text-bottom;
21
+ fill: currentColor;
22
+ margin-right: 7px;
23
+ flex: none;
24
+ }
25
+
26
+ .color-icon-success {
27
+ color: var(--color-success-fg) !important;
28
+ }
29
+
30
+ .color-text-danger {
31
+ color: var(--color-danger-fg) !important;
32
+ }
extension/src/ui/icons.tsx ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ Copyright (c) Microsoft Corporation.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import './icons.css';
18
+ import './colors.css';
19
+
20
+ export const cross = () => {
21
+ return <svg className='octicon color-text-danger' viewBox='0 0 16 16' version='1.1' width='16' height='16' aria-hidden='true'>
22
+ <path fillRule='evenodd' d='M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z'></path>
23
+ </svg>;
24
+ };
25
+
26
+ export const check = () => {
27
+ return <svg aria-hidden='true' height='16' viewBox='0 0 16 16' version='1.1' width='16' data-view-component='true' className='octicon color-icon-success'>
28
+ <path fillRule='evenodd' d='M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z'></path>
29
+ </svg>;
30
+ };
31
+
32
+ export const copy = () => {
33
+ return <svg className='octicon' viewBox='0 0 16 16' width='16' height='16' aria-hidden='true'>
34
+ <path d='M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z'></path>
35
+ <path d='M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z'></path>
36
+ </svg>;
37
+ };
38
+
39
+ export const refresh = () => {
40
+ return <svg className='octicon' viewBox="0 0 16 16" width="16" height="16" aria-hidden='true'>
41
+ <path d="M1.705 8.005a.75.75 0 0 1 .834.656 5.5 5.5 0 0 0 9.592 2.97l-1.204-1.204a.25.25 0 0 1 .177-.427h3.646a.25.25 0 0 1 .25.25v3.646a.25.25 0 0 1-.427.177l-1.38-1.38A7.002 7.002 0 0 1 1.05 8.84a.75.75 0 0 1 .656-.834ZM8 2.5a5.487 5.487 0 0 0-4.131 1.869l1.204 1.204A.25.25 0 0 1 4.896 6H1.25A.25.25 0 0 1 1 5.75V2.104a.25.25 0 0 1 .427-.177l1.38 1.38A7.002 7.002 0 0 1 14.95 7.16a.75.75 0 0 1-1.49.178A5.5 5.5 0 0 0 8 2.5Z"></path>
42
+ </svg>;
43
+ };
extension/src/ui/status.tsx CHANGED
@@ -19,6 +19,7 @@ import { createRoot } from 'react-dom/client';
19
  import { Button, TabItem } from './tabItem';
20
 
21
  import type { TabInfo } from './tabItem';
 
22
 
23
  interface ConnectionStatus {
24
  isConnected: boolean;
@@ -97,6 +98,7 @@ const StatusApp: React.FC = () => {
97
  No MCP clients are currently connected.
98
  </div>
99
  )}
 
100
  </div>
101
  </div>
102
  );
 
19
  import { Button, TabItem } from './tabItem';
20
 
21
  import type { TabInfo } from './tabItem';
22
+ import { AuthTokenSection } from './authToken';
23
 
24
  interface ConnectionStatus {
25
  isConnected: boolean;
 
98
  No MCP clients are currently connected.
99
  </div>
100
  )}
101
+ <AuthTokenSection />
102
  </div>
103
  </div>
104
  );
extension/tests/extension.spec.ts CHANGED
@@ -304,3 +304,33 @@ test(`custom executablePath`, async ({ startClient, server, useShortConnectionTi
304
  });
305
  expect(await fs.promises.readFile(test.info().outputPath('output.txt'), 'utf8')).toContain('Custom exec args: chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html?');
306
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  });
305
  expect(await fs.promises.readFile(test.info().outputPath('output.txt'), 'utf8')).toContain('Custom exec args: chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html?');
306
  });
307
+
308
+ test(`bypass connection dialog with token`, async ({ browserWithExtension, startClient, server }) => {
309
+ const browserContext = await browserWithExtension.launch();
310
+
311
+ const page = await browserContext.newPage();
312
+ await page.goto('chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/status.html');
313
+ const token = await page.locator('.auth-token-code').textContent();
314
+ const [name, value] = token?.split('=') || [];
315
+
316
+ const { client } = await startClient({
317
+ args: [`--extension`],
318
+ extensionToken: value,
319
+ config: {
320
+ browser: {
321
+ userDataDir: browserWithExtension.userDataDir,
322
+ }
323
+ },
324
+ });
325
+
326
+ const navigateResponse = await client.callTool({
327
+ name: 'browser_navigate',
328
+ arguments: { url: server.HELLO_WORLD },
329
+ });
330
+
331
+ expect(await navigateResponse).toHaveResponse({
332
+ pageState: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`),
333
+ });
334
+
335
+
336
+ });
tests/fixtures.ts CHANGED
@@ -46,6 +46,7 @@ export type StartClient = (options?: {
46
  config?: Config,
47
  roots?: { name: string, uri: string }[],
48
  rootsResponseDelay?: number,
 
49
  }) => Promise<{ client: Client, stderr: () => string }>;
50
 
51
 
@@ -102,7 +103,7 @@ export const test = baseTest.extend<TestFixtures & TestOptions, WorkerFixtures>(
102
  };
103
  });
104
  }
105
- const { transport, stderr } = await createTransport(args, mcpMode, testInfo.outputPath('ms-playwright'));
106
  let stderrBuffer = '';
107
  stderr?.on('data', data => {
108
  if (process.env.PWMCP_DEBUG)
@@ -181,7 +182,7 @@ export const test = baseTest.extend<TestFixtures & TestOptions, WorkerFixtures>(
181
  },
182
  });
183
 
184
- async function createTransport(args: string[], mcpMode: TestOptions['mcpMode'], profilesDir: string): Promise<{
185
  transport: Transport,
186
  stderr: Stream | null,
187
  }> {
@@ -208,6 +209,7 @@ async function createTransport(args: string[], mcpMode: TestOptions['mcpMode'],
208
  DEBUG_COLORS: '0',
209
  DEBUG_HIDE_DATE: '1',
210
  PWMCP_PROFILES_DIR_FOR_TEST: profilesDir,
 
211
  },
212
  });
213
  return {
 
46
  config?: Config,
47
  roots?: { name: string, uri: string }[],
48
  rootsResponseDelay?: number,
49
+ extensionToken?: string,
50
  }) => Promise<{ client: Client, stderr: () => string }>;
51
 
52
 
 
103
  };
104
  });
105
  }
106
+ const { transport, stderr } = await createTransport(args, mcpMode, testInfo.outputPath('ms-playwright'), options?.extensionToken);
107
  let stderrBuffer = '';
108
  stderr?.on('data', data => {
109
  if (process.env.PWMCP_DEBUG)
 
182
  },
183
  });
184
 
185
+ async function createTransport(args: string[], mcpMode: TestOptions['mcpMode'], profilesDir: string, extensionToken?: string): Promise<{
186
  transport: Transport,
187
  stderr: Stream | null,
188
  }> {
 
209
  DEBUG_COLORS: '0',
210
  DEBUG_HIDE_DATE: '1',
211
  PWMCP_PROFILES_DIR_FOR_TEST: profilesDir,
212
+ ...(extensionToken ? { PLAYWRIGHT_MCP_EXTENSION_TOKEN: extensionToken } : {}),
213
  },
214
  });
215
  return {