clay-server 4.7.1-beta.5 → 4.8.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/login-browser-open.js +7 -0
- package/lib/bundled-skills/clay-agent-browser/SKILL.md +36 -0
- package/lib/clay-skill-install.js +33 -0
- package/lib/clay-skill-storage.js +49 -0
- package/lib/project-http.js +14 -5
- package/lib/project-shared-browser.js +256 -0
- package/lib/project-vendor-login.js +52 -18
- package/lib/project.js +38 -1
- package/lib/public/app.js +8 -0
- package/lib/public/css/filebrowser.css +2 -25
- package/lib/public/css/issues.css +1 -14
- package/lib/public/css/mcp-skills-workbench.css +51 -0
- package/lib/public/css/project-logs.css +0 -11
- package/lib/public/css/right-workbench.css +28 -0
- package/lib/public/css/scheduled-tasks.css +1 -1
- package/lib/public/css/shared-browser-skins.css +28 -0
- package/lib/public/css/shared-browser.css +91 -0
- package/lib/public/css/sticky-notes.css +0 -11
- package/lib/public/css/vendor-login-browser.css +63 -0
- package/lib/public/index.html +30 -22
- package/lib/public/modules/app-messages.js +7 -0
- package/lib/public/modules/app-notifications.js +1 -1
- package/lib/public/modules/extension-settings.js +110 -0
- package/lib/public/modules/mcp-skills-workbench.js +67 -0
- package/lib/public/modules/mcp-ui.js +9 -12
- package/lib/public/modules/notifications.js +2 -107
- package/lib/public/modules/shared-browser-activity.js +66 -0
- package/lib/public/modules/shared-browser-input.js +73 -0
- package/lib/public/modules/shared-browser-tabs.js +33 -0
- package/lib/public/modules/shared-browser-viewport-menu.js +76 -0
- package/lib/public/modules/shared-browser.js +214 -0
- package/lib/public/modules/sidebar-mobile.js +3 -3
- package/lib/public/modules/skills-actions.js +247 -0
- package/lib/public/modules/skills-detail.js +148 -0
- package/lib/public/modules/skills-state.js +10 -0
- package/lib/public/modules/skills.js +64 -459
- package/lib/public/modules/tool-palette-order.js +11 -13
- package/lib/public/modules/vendor-login-browser.js +243 -0
- package/lib/public/modules/vendor-login.js +23 -4
- package/lib/public/style.css +7 -0
- package/lib/sdk-bridge.js +14 -1
- package/lib/sessions.js +2 -0
- package/lib/shared-browser-activity-history.js +17 -0
- package/lib/shared-browser-policy.js +26 -0
- package/lib/shared-browser-runtime.js +82 -0
- package/lib/shared-browser-tools.js +21 -0
- package/lib/shared-browser-worker.js +160 -0
- package/lib/terminal.js +4 -0
- package/lib/vendor-login-browser-flow.js +46 -0
- package/lib/vendor-login-browser-passkeys.js +27 -0
- package/lib/vendor-login-browser-worker.js +149 -0
- package/lib/vendor-login-browser.js +162 -0
- package/lib/ws-schema.js +14 -0
- package/lib/yoke/skill-discovery.js +22 -2
- package/package.json +2 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Vendor login flow coordinator.
|
|
2
2
|
//
|
|
3
3
|
// Owns the "the vendor CLI is not logged in" recovery path end to end so the
|
|
4
|
-
// client never has to guess. One login terminal per vendor per
|
|
4
|
+
// client never has to guess. One login terminal per vendor per requester is the
|
|
5
5
|
// single source of truth: repeated auth_required events (other sessions, split
|
|
6
6
|
// panes, a burst of 401 retries) re-use that record instead of spawning more
|
|
7
7
|
// terminals.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// command, reply with vendor_login_ready
|
|
12
12
|
// PTY output/exit -> detect a successful login, restart the vendor's
|
|
13
13
|
// YOKE adapter so it re-reads the credential file,
|
|
14
|
-
//
|
|
14
|
+
// send auth_refreshed, close the terminal and browser
|
|
15
15
|
// vendor_login_cancel -> user dismissed the flow; kill the terminal
|
|
16
16
|
//
|
|
17
17
|
// The adapter restart is the actual bug fix: a Codex app-server is a long-lived
|
|
@@ -84,10 +84,20 @@ function attachVendorLogin(ctx) {
|
|
|
84
84
|
var getLinuxUserForSession = ctx.getLinuxUserForSession;
|
|
85
85
|
var refreshVendorAuthEverywhere = ctx.refreshVendorAuthEverywhere;
|
|
86
86
|
|
|
87
|
-
// vendor -> flow
|
|
87
|
+
// Requester + vendor -> flow. Credentials and sign-in views stay user-bound.
|
|
88
88
|
var _flows = Object.create(null);
|
|
89
|
+
function flowKey(ws, vendor) {
|
|
90
|
+
return JSON.stringify([ws && ws._clayUser ? String(ws._clayUser.id) : null, vendor]);
|
|
91
|
+
}
|
|
92
|
+
var browserLogin = require("./vendor-login-browser-flow").attachBrowserLogin({
|
|
93
|
+
osUsers: osUsers, sendTo: sendTo, hasTerminalPermission: hasTerminalPermission,
|
|
94
|
+
createLoginBrowser: ctx.createLoginBrowser,
|
|
95
|
+
flowKey: flowKey,
|
|
96
|
+
cancel: function (vendor) { closeFlowTerminal(discardFlow(vendor)); broadcastState(); },
|
|
97
|
+
});
|
|
89
98
|
|
|
90
99
|
function loginCommandFor(vendor) {
|
|
100
|
+
if (vendor === "claude") return "claude auth login";
|
|
91
101
|
var info = vendorRegistry.getVendorInfo(vendor);
|
|
92
102
|
return (info && info.loginCommand) || "claude login";
|
|
93
103
|
}
|
|
@@ -97,30 +107,34 @@ function attachVendorLogin(ctx) {
|
|
|
97
107
|
return (info && info.displayName) || vendor || "Vendor";
|
|
98
108
|
}
|
|
99
109
|
|
|
100
|
-
function listFlows() {
|
|
110
|
+
function listFlows(ws) {
|
|
101
111
|
var result = [];
|
|
102
112
|
var vendors = Object.keys(_flows);
|
|
103
113
|
for (var i = 0; i < vendors.length; i++) {
|
|
104
114
|
var flow = _flows[vendors[i]];
|
|
115
|
+
if (ws && flowKey(ws, flow.vendor) !== vendors[i]) continue;
|
|
105
116
|
result.push({
|
|
106
117
|
vendor: flow.vendor,
|
|
107
118
|
terminalId: flow.terminalId,
|
|
108
119
|
startedAt: flow.startedAt,
|
|
109
120
|
completed: !!flow.completed,
|
|
121
|
+
browserId: flow.browser ? flow.browser.id : null,
|
|
110
122
|
});
|
|
111
123
|
}
|
|
112
124
|
return result;
|
|
113
125
|
}
|
|
114
126
|
|
|
115
127
|
function broadcastState() {
|
|
128
|
+
if (ctx.clients) { ctx.clients.forEach(sendStateTo); return; }
|
|
116
129
|
send({ type: "vendor_login_state", slug: slug, flows: listFlows() });
|
|
117
130
|
}
|
|
118
131
|
|
|
119
132
|
function sendStateTo(ws) {
|
|
120
|
-
sendTo(ws, { type: "vendor_login_state", slug: slug, flows: listFlows() });
|
|
133
|
+
sendTo(ws, { type: "vendor_login_state", slug: slug, flows: hasTerminalPermission(ws) ? listFlows(ws) : [] });
|
|
121
134
|
}
|
|
122
135
|
|
|
123
136
|
function hasTerminalPermission(ws) {
|
|
137
|
+
if (ctx.requestAccess) return ctx.requestAccess.canAccessProject(ws, slug) && ctx.requestAccess.hasPermission(ws, "terminal");
|
|
124
138
|
if (!ws || !ws._clayUser) return true;
|
|
125
139
|
if (!usersModule || typeof usersModule.getEffectivePermissions !== "function") return true;
|
|
126
140
|
var perms = usersModule.getEffectivePermissions(ws._clayUser, osUsers);
|
|
@@ -164,6 +178,8 @@ function attachVendorLogin(ctx) {
|
|
|
164
178
|
var flow = _flows[vendor];
|
|
165
179
|
if (!flow) return null;
|
|
166
180
|
delete _flows[vendor];
|
|
181
|
+
if (flow.browser) flow.browser.close();
|
|
182
|
+
if (flow.expiryTimer) clearTimeout(flow.expiryTimer);
|
|
167
183
|
if (flow.closeTimer) {
|
|
168
184
|
clearTimeout(flow.closeTimer);
|
|
169
185
|
flow.closeTimer = null;
|
|
@@ -223,14 +239,17 @@ function attachVendorLogin(ctx) {
|
|
|
223
239
|
var refresh = Promise.resolve(false);
|
|
224
240
|
if (succeeded) {
|
|
225
241
|
refresh = typeof refreshVendorAuthEverywhere === "function"
|
|
226
|
-
? refreshVendorAuthEverywhere(vendor, flow.linuxUser || null)
|
|
227
|
-
: refreshVendorAuth(vendor, flow.linuxUser || null);
|
|
242
|
+
? refreshVendorAuthEverywhere(flow.vendor, flow.linuxUser || null)
|
|
243
|
+
: refreshVendorAuth(flow.vendor, flow.linuxUser || null);
|
|
228
244
|
}
|
|
229
245
|
refresh.then(function (restarted) {
|
|
246
|
+
if (_flows[vendor] !== flow) return;
|
|
230
247
|
discardFlow(vendor);
|
|
231
248
|
closeFlowTerminal(flow);
|
|
232
249
|
if (succeeded) {
|
|
233
|
-
|
|
250
|
+
var message = { type: "auth_refreshed", slug: slug, vendor: flow.vendor, adapterRestarted: !!restarted };
|
|
251
|
+
if (ctx.clients) ctx.clients.forEach(function (ws) { if (flowKey(ws, flow.vendor) === vendor && hasTerminalPermission(ws)) sendTo(ws, message); });
|
|
252
|
+
else send(message);
|
|
234
253
|
}
|
|
235
254
|
broadcastState();
|
|
236
255
|
});
|
|
@@ -240,10 +259,11 @@ function attachVendorLogin(ctx) {
|
|
|
240
259
|
var flow = _flows[vendor];
|
|
241
260
|
if (!flow || flow.finishing || flow.succeeded) return;
|
|
242
261
|
flow.outputTail = (flow.outputTail + stripAnsi(chunk)).slice(-OUTPUT_TAIL_MAX);
|
|
262
|
+
if (flow.browser) flow.browser.output(stripAnsi(chunk));
|
|
243
263
|
if (!looksLikeLoginSuccess(flow.outputTail)) return;
|
|
244
264
|
|
|
245
265
|
flow.succeeded = true;
|
|
246
|
-
console.log("[vendor-login] Detected successful " + vendor + " login in " + slug);
|
|
266
|
+
console.log("[vendor-login] Detected successful " + flow.vendor + " login in " + slug);
|
|
247
267
|
// Let the success line land on screen before the terminal disappears.
|
|
248
268
|
flow.closeTimer = setTimeout(function () {
|
|
249
269
|
finishFlow(vendor, true);
|
|
@@ -261,10 +281,15 @@ function attachVendorLogin(ctx) {
|
|
|
261
281
|
}
|
|
262
282
|
|
|
263
283
|
function startFlow(ws, vendor, msg) {
|
|
284
|
+
var key = flowKey(ws, vendor);
|
|
264
285
|
var identity = resolveLoginIdentity(ws, msg);
|
|
265
286
|
var command = loginCommandFor(vendor);
|
|
287
|
+
var browserMode = msg.mode === "browser" && (vendor === "claude" || vendor === "codex");
|
|
288
|
+
if (browserMode) command = vendor === "codex" ? "codex login" : "claude auth login";
|
|
266
289
|
var flow = {
|
|
267
290
|
vendor: vendor,
|
|
291
|
+
key: key,
|
|
292
|
+
ownerId: ws && ws._clayUser ? String(ws._clayUser.id) : null,
|
|
268
293
|
terminalId: null,
|
|
269
294
|
startedAt: Date.now(),
|
|
270
295
|
linuxUser: identity.linuxUser,
|
|
@@ -277,16 +302,18 @@ function attachVendorLogin(ctx) {
|
|
|
277
302
|
};
|
|
278
303
|
// Registered before create() so the PTY's first output chunk (hooks fire
|
|
279
304
|
// synchronously from spawn) already finds the record.
|
|
280
|
-
_flows[
|
|
305
|
+
_flows[key] = flow;
|
|
281
306
|
|
|
282
307
|
var terminal = null;
|
|
283
308
|
try {
|
|
309
|
+
if (browserMode) browserLogin.prepare(ws, flow, identity);
|
|
284
310
|
terminal = tm.create(100, 30, identity.osUserInfo, ws, {
|
|
285
311
|
initialInput: command + "\n",
|
|
286
312
|
title: vendorDisplayName(vendor) + " login",
|
|
287
313
|
kind: "vendor-login",
|
|
288
|
-
|
|
289
|
-
|
|
314
|
+
loginBrowserEnv: flow.browser ? flow.browser.env : null,
|
|
315
|
+
onData: function (data) { if (_flows[key] === flow) handleFlowOutput(key, data); },
|
|
316
|
+
onExit: function () { if (_flows[key] === flow) handleFlowExit(key); },
|
|
290
317
|
});
|
|
291
318
|
} catch (e) {
|
|
292
319
|
console.error("[vendor-login] Failed to spawn " + vendor + " login terminal in " + slug + ":",
|
|
@@ -294,17 +321,18 @@ function attachVendorLogin(ctx) {
|
|
|
294
321
|
}
|
|
295
322
|
|
|
296
323
|
if (!terminal) {
|
|
297
|
-
|
|
324
|
+
discardFlow(key);
|
|
298
325
|
return null;
|
|
299
326
|
}
|
|
300
327
|
|
|
301
328
|
flow.terminalId = terminal.id;
|
|
302
|
-
tm.attach(terminal.id, ws);
|
|
329
|
+
if (!flow.browser) tm.attach(terminal.id, ws);
|
|
303
330
|
send({ type: "term_list", terminals: tm.list() });
|
|
304
331
|
return flow;
|
|
305
332
|
}
|
|
306
333
|
|
|
307
334
|
function handleVendorLoginMessage(ws, msg) {
|
|
335
|
+
if (browserLogin.handle(ws, msg, _flows)) return true;
|
|
308
336
|
if (msg.type === "vendor_login_state_request") {
|
|
309
337
|
sendStateTo(ws);
|
|
310
338
|
return true;
|
|
@@ -312,7 +340,7 @@ function attachVendorLogin(ctx) {
|
|
|
312
340
|
|
|
313
341
|
if (msg.type === "vendor_login_cancel") {
|
|
314
342
|
var cancelVendor = String(msg.vendor || "");
|
|
315
|
-
var cancelled = discardFlow(cancelVendor);
|
|
343
|
+
var cancelled = discardFlow(flowKey(ws, cancelVendor));
|
|
316
344
|
if (cancelled) closeFlowTerminal(cancelled);
|
|
317
345
|
broadcastState();
|
|
318
346
|
return true;
|
|
@@ -329,10 +357,11 @@ function attachVendorLogin(ctx) {
|
|
|
329
357
|
return true;
|
|
330
358
|
}
|
|
331
359
|
|
|
332
|
-
var
|
|
360
|
+
var key = flowKey(ws, vendor);
|
|
361
|
+
var existing = _flows[key];
|
|
333
362
|
if (existing && !isFlowTerminalAlive(existing)) {
|
|
334
363
|
// Terminal died without its exit hook clearing the record.
|
|
335
|
-
discardFlow(
|
|
364
|
+
discardFlow(key);
|
|
336
365
|
existing = null;
|
|
337
366
|
}
|
|
338
367
|
|
|
@@ -341,13 +370,14 @@ function attachVendorLogin(ctx) {
|
|
|
341
370
|
// session or split pane) must not pop a second prompt; it only learns
|
|
342
371
|
// that a flow is already running. A deliberate request re-attaches.
|
|
343
372
|
if (!msg.auto) {
|
|
344
|
-
tm.attach(existing.terminalId, ws);
|
|
373
|
+
if (!existing.browser) tm.attach(existing.terminalId, ws);
|
|
345
374
|
sendTo(ws, {
|
|
346
375
|
type: "vendor_login_ready",
|
|
347
376
|
slug: slug,
|
|
348
377
|
vendor: vendor,
|
|
349
378
|
terminalId: existing.terminalId,
|
|
350
379
|
reused: true,
|
|
380
|
+
browserId: existing.browser ? existing.browser.id : null,
|
|
351
381
|
});
|
|
352
382
|
}
|
|
353
383
|
sendStateTo(ws);
|
|
@@ -371,6 +401,7 @@ function attachVendorLogin(ctx) {
|
|
|
371
401
|
vendor: vendor,
|
|
372
402
|
terminalId: started.terminalId,
|
|
373
403
|
reused: false,
|
|
404
|
+
browserId: started.browser ? started.browser.id : null,
|
|
374
405
|
});
|
|
375
406
|
broadcastState();
|
|
376
407
|
return true;
|
|
@@ -408,6 +439,9 @@ function attachVendorLogin(ctx) {
|
|
|
408
439
|
forgetTerminal: forgetTerminal,
|
|
409
440
|
isLoginTerminal: isLoginTerminal,
|
|
410
441
|
refreshVendorAuth: refreshVendorAuth,
|
|
442
|
+
destroy: function () {
|
|
443
|
+
Object.keys(_flows).forEach(function (vendor) { closeFlowTerminal(discardFlow(vendor)); });
|
|
444
|
+
},
|
|
411
445
|
};
|
|
412
446
|
}
|
|
413
447
|
|
package/lib/project.js
CHANGED
|
@@ -62,6 +62,7 @@ var { attachDriverContinuation } = require("./project-driver-continuation");
|
|
|
62
62
|
var driverContinuationAccess = require("./driver-continuation-access");
|
|
63
63
|
var { attachSessionNotes, composeSystemPrompts } = require("./project-session-notes");
|
|
64
64
|
var { attachSessionDocument } = require("./project-session-document");
|
|
65
|
+
var { attachSharedBrowser } = require("./project-shared-browser");
|
|
65
66
|
var { attachCapsuleCatalog } = require("./project-capsule-catalog");
|
|
66
67
|
var { attachProjectWorkspaceQuery } = require("./project-workspace-query");
|
|
67
68
|
var { attachProjectLogs } = require("./project-logs");
|
|
@@ -753,6 +754,15 @@ function createProjectContext(opts) {
|
|
|
753
754
|
}
|
|
754
755
|
},
|
|
755
756
|
});
|
|
757
|
+
var _sharedBrowser = attachSharedBrowser({
|
|
758
|
+
slug: slug, isMate: isMate, sm: sm, clients: clients, sendTo: sendTo,
|
|
759
|
+
usersModule: usersModule, requestAccess: _requestAccess, osUsers: osUsers,
|
|
760
|
+
getSessionForWs: getSessionForWs,
|
|
761
|
+
getIdentity: function (session) {
|
|
762
|
+
var linuxUser = getLinuxUserForSession(session);
|
|
763
|
+
return linuxUser ? resolveOsUserInfo(linuxUser) : null;
|
|
764
|
+
},
|
|
765
|
+
});
|
|
756
766
|
var _sessionDocument = attachSessionDocument({
|
|
757
767
|
cwd: cwd,
|
|
758
768
|
isMate: isMate,
|
|
@@ -1045,6 +1055,9 @@ function createProjectContext(opts) {
|
|
|
1045
1055
|
}
|
|
1046
1056
|
}
|
|
1047
1057
|
|
|
1058
|
+
var sharedBrowserConfig = _sharedBrowser.createMcpServer(adapter);
|
|
1059
|
+
if (sharedBrowserConfig) servers["clay-shared-browser"] = sharedBrowserConfig;
|
|
1060
|
+
|
|
1048
1061
|
// Explicit agent signal for user-requested Markdown presentation.
|
|
1049
1062
|
if (!isMate) {
|
|
1050
1063
|
try {
|
|
@@ -1263,6 +1276,11 @@ function createProjectContext(opts) {
|
|
|
1263
1276
|
}
|
|
1264
1277
|
continue;
|
|
1265
1278
|
}
|
|
1279
|
+
if (name === "clay-shared-browser" && forSession) {
|
|
1280
|
+
var boundBrowser = _sharedBrowser.createMcpServer(adapter, forSession);
|
|
1281
|
+
if (boundBrowser) { filtered[name] = boundBrowser; hasAny = true; }
|
|
1282
|
+
continue;
|
|
1283
|
+
}
|
|
1266
1284
|
if (name === "clay-documents" && forSession) {
|
|
1267
1285
|
try {
|
|
1268
1286
|
var boundDocuments = _sessionDocument.createMcpServer(adapter, forSession);
|
|
@@ -1374,6 +1392,7 @@ function createProjectContext(opts) {
|
|
|
1374
1392
|
_sessionHandoff.getSystemPrompt(session),
|
|
1375
1393
|
_driverContinuation.getSystemPrompt(session),
|
|
1376
1394
|
_sessionDocument.getSystemPrompt(session),
|
|
1395
|
+
_sharedBrowser.getSystemPrompt(session),
|
|
1377
1396
|
_capsuleCatalog.getSystemPrompt(session),
|
|
1378
1397
|
_projectLogs.getSystemPrompt(session),
|
|
1379
1398
|
_issues.getSystemPrompt(session),
|
|
@@ -1393,6 +1412,7 @@ function createProjectContext(opts) {
|
|
|
1393
1412
|
.concat(_sessionHandoff.getToolDefs(session))
|
|
1394
1413
|
.concat(_driverContinuation.getToolDefs(session, Number(session._sdkQueryGeneration || 0)))
|
|
1395
1414
|
.concat(_sessionDocument.getToolDefs(session))
|
|
1415
|
+
.concat(_sharedBrowser.getToolDefs(session))
|
|
1396
1416
|
.concat(_debateProposal.getToolDefs(session))
|
|
1397
1417
|
.concat(_mateCreationProposal.getToolDefs(session))
|
|
1398
1418
|
.concat(_workspaceQuery.getDynamicToolDefs(session))
|
|
@@ -1821,6 +1841,7 @@ function createProjectContext(opts) {
|
|
|
1821
1841
|
if (_shellCommand.handleShellCommand(ws, msg)) return;
|
|
1822
1842
|
|
|
1823
1843
|
// --- Vendor login flow (delegated to project-vendor-login.js) ---
|
|
1844
|
+
if (_sharedBrowser.handleMessage(ws, msg)) return;
|
|
1824
1845
|
if (_vendorLogin.handleVendorLoginMessage(ws, msg)) return;
|
|
1825
1846
|
|
|
1826
1847
|
// --- Notes, terminals, context, user message (delegated to project-user-message.js) ---
|
|
@@ -2204,6 +2225,8 @@ function createProjectContext(opts) {
|
|
|
2204
2225
|
|
|
2205
2226
|
// --- Vendor login flow (delegated to project-vendor-login.js) ---
|
|
2206
2227
|
var _vendorLogin = attachVendorLogin({
|
|
2228
|
+
requestAccess: _requestAccess,
|
|
2229
|
+
clients: clients,
|
|
2207
2230
|
slug: slug,
|
|
2208
2231
|
osUsers: osUsers,
|
|
2209
2232
|
sm: sm,
|
|
@@ -2342,6 +2365,11 @@ function createProjectContext(opts) {
|
|
|
2342
2365
|
inputSchema: normalizeToolSchema(continuationTools[cti].inputSchema),
|
|
2343
2366
|
});
|
|
2344
2367
|
}
|
|
2368
|
+
var sharedTools = _sharedBrowser.getToolDefs(boundSession);
|
|
2369
|
+
sharedTools.forEach(function (definition) {
|
|
2370
|
+
tools.push({ server: "clay-shared-browser", name: definition.name, description: definition.description,
|
|
2371
|
+
inputSchema: normalizeToolSchema(definition.inputSchema) });
|
|
2372
|
+
});
|
|
2345
2373
|
var documentTools = _sessionDocument.getToolDefs(boundSession);
|
|
2346
2374
|
for (var dti = 0; dti < documentTools.length; dti++) {
|
|
2347
2375
|
tools.push({
|
|
@@ -2385,6 +2413,7 @@ function createProjectContext(opts) {
|
|
|
2385
2413
|
var inAppNames = Object.keys(localMcp);
|
|
2386
2414
|
for (var i = 0; i < inAppNames.length; i++) {
|
|
2387
2415
|
if (inAppNames[i] === "clay-sessions" || inAppNames[i] === "clay-notes" || inAppNames[i] === "clay-handoff" || inAppNames[i] === "clay-documents") continue;
|
|
2416
|
+
if (inAppNames[i] === "clay-shared-browser") continue;
|
|
2388
2417
|
// Debate tools were added above only when this request had an
|
|
2389
2418
|
// exact session. Never duplicate them or advertise the static,
|
|
2390
2419
|
// intentionally unbound proposal server.
|
|
@@ -2444,6 +2473,12 @@ function createProjectContext(opts) {
|
|
|
2444
2473
|
}
|
|
2445
2474
|
}
|
|
2446
2475
|
}
|
|
2476
|
+
if (boundSession && serverName === "clay-shared-browser") {
|
|
2477
|
+
var sharedTools = _sharedBrowser.getToolDefs(boundSession);
|
|
2478
|
+
for (var bti = 0; bti < sharedTools.length; bti++) {
|
|
2479
|
+
if (sharedTools[bti].name === toolName) return Promise.resolve(sharedTools[bti].handler(args || {}));
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2447
2482
|
if (boundSession && serverName === "clay-documents") {
|
|
2448
2483
|
var documentTools = _sessionDocument.getToolDefs(boundSession);
|
|
2449
2484
|
for (var dti = 0; dti < documentTools.length; dti++) {
|
|
@@ -2482,7 +2517,7 @@ function createProjectContext(opts) {
|
|
|
2482
2517
|
if (boundSession && serverName === "clay-knowledge") {
|
|
2483
2518
|
return _mateKnowledge.callBridgeTool(boundSession, toolName, args);
|
|
2484
2519
|
}
|
|
2485
|
-
if (serverName === "clay-sessions" || serverName === "clay-notes" || serverName === "clay-handoff" || serverName === "clay-documents" || serverName === "clay-debate" || serverName === "clay-mate-creation" || serverName === "clay-workspace" || serverName === "clay-history" || serverName === "clay-github" || serverName === "clay-issues" || serverName === "clay-logs" || serverName === "clay-knowledge") {
|
|
2520
|
+
if (serverName === "clay-sessions" || serverName === "clay-notes" || serverName === "clay-handoff" || serverName === "clay-documents" || serverName === "clay-shared-browser" || serverName === "clay-debate" || serverName === "clay-mate-creation" || serverName === "clay-workspace" || serverName === "clay-history" || serverName === "clay-github" || serverName === "clay-issues" || serverName === "clay-logs" || serverName === "clay-knowledge") {
|
|
2486
2521
|
return Promise.reject(new Error("Session-bound tool requires a valid Clay session: " + serverName + "/" + toolName));
|
|
2487
2522
|
}
|
|
2488
2523
|
if (sessionOnly) return Promise.reject(new Error("Session tool not found: " + serverName + "/" + toolName));
|
|
@@ -2600,6 +2635,8 @@ function createProjectContext(opts) {
|
|
|
2600
2635
|
|
|
2601
2636
|
// --- Destroy ---
|
|
2602
2637
|
function destroy() {
|
|
2638
|
+
_sharedBrowser.destroy();
|
|
2639
|
+
_vendorLogin.destroy();
|
|
2603
2640
|
if (_scheduledMessages) _scheduledMessages.shutdown();
|
|
2604
2641
|
if (_autonomousRun) _autonomousRun.shutdown();
|
|
2605
2642
|
_splitGroups.shutdown();
|
package/lib/public/app.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { initSharedBrowser } from './modules/shared-browser.js';
|
|
1
2
|
import { initGenerationStop } from './modules/generation-stop.js';
|
|
2
3
|
import { initIssueLinks } from './modules/clay-issue-links.js';
|
|
3
4
|
import { initIssues, closeIssues } from './modules/issues.js';
|
|
@@ -268,6 +269,12 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
268
269
|
// --- Initialize store (single source of truth for client state) ---
|
|
269
270
|
createStore({
|
|
270
271
|
networkSettings: { loaded: false, busy: false, confirming: false, draft: "", saved: "", status: "", errorKind: "", requestId: 0 },
|
|
272
|
+
vendorLoginBrowser: null,
|
|
273
|
+
sharedBrowser: null,
|
|
274
|
+
sharedBrowsers: [],
|
|
275
|
+
mcpSkillsWorkbench: {},
|
|
276
|
+
sharedBrowserOpen: false,
|
|
277
|
+
sharedBrowserUi: {},
|
|
271
278
|
// session / routing
|
|
272
279
|
basePath: basePath,
|
|
273
280
|
wsPath: wsPath,
|
|
@@ -1174,6 +1181,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
1174
1181
|
fileViewerEl: $("file-viewer"),
|
|
1175
1182
|
});
|
|
1176
1183
|
initGitPanel();
|
|
1184
|
+
initSharedBrowser();
|
|
1177
1185
|
initWorkerPaneLock();
|
|
1178
1186
|
|
|
1179
1187
|
// --- Terminal ---
|
|
@@ -1054,12 +1054,6 @@
|
|
|
1054
1054
|
flex-direction: column;
|
|
1055
1055
|
overflow: hidden;
|
|
1056
1056
|
flex-shrink: 0;
|
|
1057
|
-
box-shadow:
|
|
1058
|
-
4px 8px 18px rgba(var(--shadow-rgb), 0.14),
|
|
1059
|
-
2px 2px 4px rgba(var(--shadow-rgb), 0.08),
|
|
1060
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
|
1061
|
-
animation: workbench-panel-in 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
1062
|
-
transition: max-width 0.2s ease, width 0.2s ease, box-shadow 0.2s ease;
|
|
1063
1057
|
}
|
|
1064
1058
|
#file-viewer.hidden { display: none; }
|
|
1065
1059
|
#file-viewer.file-viewer-wide {
|
|
@@ -1078,15 +1072,10 @@
|
|
|
1078
1072
|
border: 0;
|
|
1079
1073
|
border-radius: 0;
|
|
1080
1074
|
background: var(--bg);
|
|
1081
|
-
box-shadow: none;
|
|
1082
|
-
animation: none;
|
|
1083
1075
|
}
|
|
1084
1076
|
}
|
|
1085
1077
|
|
|
1086
|
-
|
|
1087
|
-
from { opacity: 0; transform: translateX(10px) scale(0.995); }
|
|
1088
|
-
to { opacity: 1; transform: translateX(0) scale(1); }
|
|
1089
|
-
}
|
|
1078
|
+
|
|
1090
1079
|
|
|
1091
1080
|
/* Narrow screens: full overlay */
|
|
1092
1081
|
@media (max-width: 1023px) {
|
|
@@ -2026,11 +2015,6 @@
|
|
|
2026
2015
|
flex-direction: column;
|
|
2027
2016
|
overflow: hidden;
|
|
2028
2017
|
flex-shrink: 0;
|
|
2029
|
-
box-shadow:
|
|
2030
|
-
4px 8px 18px rgba(var(--shadow-rgb), 0.14),
|
|
2031
|
-
2px 2px 4px rgba(var(--shadow-rgb), 0.08),
|
|
2032
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
|
2033
|
-
animation: workbench-panel-in 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
2034
2018
|
}
|
|
2035
2019
|
#terminal-container.hidden { display: none; }
|
|
2036
2020
|
#terminal-container.panel-fullscreen {
|
|
@@ -2041,17 +2025,10 @@
|
|
|
2041
2025
|
margin: 0;
|
|
2042
2026
|
border: 0;
|
|
2043
2027
|
border-radius: 0;
|
|
2044
|
-
box-shadow: none;
|
|
2045
|
-
animation: none;
|
|
2046
2028
|
}
|
|
2047
2029
|
}
|
|
2048
2030
|
|
|
2049
|
-
|
|
2050
|
-
#file-viewer,
|
|
2051
|
-
#terminal-container {
|
|
2052
|
-
animation: none;
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2031
|
+
|
|
2055
2032
|
|
|
2056
2033
|
/* Narrow screens: full overlay */
|
|
2057
2034
|
@media (max-width: 1023px) {
|
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
#issues-panel { position: relative; width: 50%; max-width: 720px; min-width: 360px; height: calc(100% - 16px); margin: 8px 8px 8px 10px; min-height: 0; align-self: center; display: flex; flex-direction: column; background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; flex-shrink: 0; }
|
|
2
|
-
@media (min-width: 1024px) {
|
|
3
|
-
#issues-panel {
|
|
4
|
-
box-shadow:
|
|
5
|
-
4px 8px 18px rgba(var(--shadow-rgb), 0.14),
|
|
6
|
-
2px 2px 4px rgba(var(--shadow-rgb), 0.08),
|
|
7
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
|
8
|
-
animation: workbench-panel-in 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
9
|
-
transition: max-width 0.2s ease, width 0.2s ease, box-shadow 0.2s ease;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
@media (prefers-reduced-motion: reduce) {
|
|
13
|
-
#issues-panel { animation: none; }
|
|
14
|
-
}
|
|
15
2
|
#issues-panel.hidden { display: none; }
|
|
16
3
|
#issues-panel.issues-wide { width: 70%; max-width: 1200px; }
|
|
17
|
-
#issues-panel.panel-fullscreen { width: 100%; max-width: none; min-width: 0; height: 100%; margin: 0; border: 0; border-radius: 0;
|
|
4
|
+
#issues-panel.panel-fullscreen { width: 100%; max-width: none; min-width: 0; height: 100%; margin: 0; border: 0; border-radius: 0; }
|
|
18
5
|
#issues-panel header { height: 48px; box-sizing: border-box; display: flex; align-items: center; gap: 12px; padding: 0 16px; border-bottom: 1px solid var(--border-subtle); flex: 0 0 auto; }
|
|
19
6
|
#issues-panel .issues-title { display: inline-flex; align-items: center; gap: 7px; font-weight: 700; }
|
|
20
7
|
#issues-panel .issues-title .lucide { width: 15px; color: var(--accent); }
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
.mcp-skills-workbench { display:flex; flex-direction:column; flex-shrink:0; width:50%; max-width:720px; min-width:360px; height:calc(100% - 16px); align-self:center; margin:8px 8px 8px 10px; border:1px solid var(--border); border-radius:12px; background:var(--bg); overflow:hidden; }
|
|
2
|
+
.mcp-skills-workbench.hidden { display:none; }
|
|
3
|
+
.mcp-skills-workbench.workbench-wide { width:70%; max-width:1200px; }
|
|
4
|
+
.mcp-skills-workbench.panel-fullscreen { width:100%; max-width:none; min-width:0; height:100%; margin:0; border:0; border-radius:0; }
|
|
5
|
+
.mcp-skills-header { display:flex; align-items:center; justify-content:space-between; padding:12px 14px; border-bottom:1px solid var(--border); }
|
|
6
|
+
.mcp-skills-header strong,.mcp-skills-header>div { display:flex; align-items:center; gap:8px; font-size:12px; font-weight:600; }
|
|
7
|
+
.mcp-skills-header svg { width:15px; height:15px; }
|
|
8
|
+
.mcp-skills-header button { display:flex; padding:6px; background:transparent; border:0; border-radius:6px; color:var(--text-muted); cursor:pointer; }
|
|
9
|
+
.mcp-skills-header button:hover { background:var(--bg-alt); color:var(--text); }
|
|
10
|
+
.mcp-skills-tabs { display:flex; padding:0 16px; border-bottom:1px solid var(--border); gap:24px; flex-shrink:0; }
|
|
11
|
+
.mcp-skills-tabs button { padding:13px 0 11px; background:none; border:0; border-bottom:2px solid transparent; color:var(--text-muted); font:inherit; font-size:12px; cursor:pointer; }
|
|
12
|
+
.mcp-skills-tabs button[aria-selected="true"] { color:var(--text); border-bottom-color:var(--accent); }
|
|
13
|
+
.mcp-skills-tabs button[hidden] { display:none; }
|
|
14
|
+
#mcp-skills-workbench #skills-modal,#mcp-skills-workbench #mcp-modal { position:static; inset:auto; z-index:auto; display:flex; flex:1; min-height:0; align-items:stretch; justify-content:stretch; }
|
|
15
|
+
#mcp-skills-workbench #skills-modal.hidden,#mcp-skills-workbench #mcp-modal.hidden { display:none; }
|
|
16
|
+
#mcp-skills-workbench .skills-dialog,#mcp-skills-workbench .mcp-dialog { width:100%; max-width:none; min-width:0; height:100%; max-height:none; padding:0; display:flex; flex-direction:column; overflow:hidden; }
|
|
17
|
+
#mcp-skills-workbench .mcp-header,#mcp-skills-workbench #skills-modal-close { display:none; }
|
|
18
|
+
#mcp-skills-workbench .skills-header { padding:12px 16px 0; }
|
|
19
|
+
#mcp-skills-workbench .skills-title { font-size:12px; font-weight:500; }
|
|
20
|
+
#mcp-skills-workbench .skills-powered-by { font-size:10px; }
|
|
21
|
+
#mcp-skills-workbench .skills-search { margin:12px 16px 0; }
|
|
22
|
+
#mcp-skills-workbench .skills-tabs { padding:8px 16px 0; gap:0; overflow-x:auto; }
|
|
23
|
+
#mcp-skills-workbench .skills-tab { padding:9px 10px; font-size:11px; white-space:nowrap; }
|
|
24
|
+
#mcp-skills-workbench .skills-content,#mcp-skills-workbench .mcp-content { flex:1; min-height:0; overflow-y:auto; padding:16px; }
|
|
25
|
+
#mcp-skills-workbench .skills-detail { display:flex; flex-direction:column; gap:24px; height:auto; min-height:0; }
|
|
26
|
+
#mcp-skills-workbench .skills-detail-main { min-width:0; flex:none; border-right:0; overflow:visible; }
|
|
27
|
+
#mcp-skills-workbench .skills-detail-sidebar { width:auto; border-left:0; padding-left:0; border-top:1px solid var(--border); padding-top:20px; }
|
|
28
|
+
#mcp-skills-workbench .skills-detail-name { font-size:24px; overflow-wrap:anywhere; }
|
|
29
|
+
#mcp-skills-workbench .skills-installed-item { align-items:flex-start; }
|
|
30
|
+
#mcp-skills-workbench .skills-item { padding:12px 0; }
|
|
31
|
+
#mcp-skills-workbench .mcp-server-row { flex-wrap:wrap; }
|
|
32
|
+
#mcp-skills-workbench .mcp-server-perm-reason { flex-basis:100%; padding-left:26px; }
|
|
33
|
+
#mcp-skills-workbench button:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
|
|
34
|
+
@media (max-width:1023px) { .mcp-skills-workbench,.mcp-skills-workbench.workbench-wide { position:fixed; inset:0; z-index:160; width:100%; max-width:none; min-width:0; height:100dvh; margin:0; border:0; border-radius:0; padding-top:env(safe-area-inset-top); padding-bottom:env(safe-area-inset-bottom); } }
|
|
35
|
+
|
|
36
|
+
#mcp-skills-workbench .skills-detail-main > .skills-detail-md-wrap { flex:none; overflow:visible; padding:0; }
|
|
37
|
+
#mcp-skills-workbench .skills-detail-main > .skills-detail-name,#mcp-skills-workbench .skills-detail-main > .skills-detail-desc,#mcp-skills-workbench .skills-detail-main > .skills-detail-cmd { margin-left:0; margin-right:0; }
|
|
38
|
+
|
|
39
|
+
#mcp-skills-workbench .skills-detail-cmd code { min-width:0; overflow-x:auto; }
|
|
40
|
+
|
|
41
|
+
#mcp-skills-workbench .ext-pill-wrap { display:block; flex-shrink:0; padding:12px 16px; border-bottom:1px solid var(--border); max-height:55%; overflow-y:auto; }
|
|
42
|
+
#mcp-skills-workbench .top-bar-ext-btn { padding:8px 10px; border-radius:6px; gap:6px; }
|
|
43
|
+
#mcp-skills-workbench .top-bar-ext-btn::after { content:"Setup"; margin-left:8px; color:var(--text-muted); font-weight:400; }
|
|
44
|
+
#mcp-skills-workbench .top-bar-ext-btn.ext-connected::after { content:"Connected"; color:var(--success); }
|
|
45
|
+
#mcp-skills-workbench .ext-popover { position:static; width:auto; margin-top:12px; padding:12px; box-shadow:none; border-radius:8px; }
|
|
46
|
+
|
|
47
|
+
#mcp-skills-workbench .ext-feature-summary { margin:8px 0 0; font-size:12px; line-height:1.5; color:var(--text-muted); }
|
|
48
|
+
#mcp-skills-workbench .ext-popover-desc p { margin:0 0 8px; }
|
|
49
|
+
#mcp-skills-workbench .ext-feature-list { margin:0 0 16px; padding-left:18px; }
|
|
50
|
+
#mcp-skills-workbench .ext-feature-list li { margin-bottom:8px; line-height:1.6; }
|
|
51
|
+
#mcp-skills-workbench .ext-feature-list strong { color:var(--text); font-weight:500; }
|
|
@@ -18,12 +18,6 @@
|
|
|
18
18
|
flex-direction: column;
|
|
19
19
|
overflow: hidden;
|
|
20
20
|
flex-shrink: 0;
|
|
21
|
-
box-shadow:
|
|
22
|
-
4px 8px 18px rgba(var(--shadow-rgb), 0.14),
|
|
23
|
-
2px 2px 4px rgba(var(--shadow-rgb), 0.08),
|
|
24
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
|
25
|
-
animation: workbench-panel-in 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
26
|
-
transition: max-width 0.2s ease, width 0.2s ease, box-shadow 0.2s ease;
|
|
27
21
|
}
|
|
28
22
|
#project-logs-panel.hidden { display: none; }
|
|
29
23
|
#project-logs-panel.project-logs-wide {
|
|
@@ -38,14 +32,9 @@
|
|
|
38
32
|
margin: 0;
|
|
39
33
|
border: 0;
|
|
40
34
|
border-radius: 0;
|
|
41
|
-
box-shadow: none;
|
|
42
|
-
animation: none;
|
|
43
35
|
}
|
|
44
36
|
}
|
|
45
37
|
|
|
46
|
-
@media (prefers-reduced-motion: reduce) {
|
|
47
|
-
#project-logs-panel { animation: none; }
|
|
48
|
-
}
|
|
49
38
|
|
|
50
39
|
/* Narrow screens: full overlay, like the other workbench tools. */
|
|
51
40
|
@media (max-width: 1023px) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* Shared desktop workbench elevation and motion. Keep panel content styles local. */
|
|
2
|
+
@keyframes workbench-panel-in {
|
|
3
|
+
from { opacity: 0; transform: translateX(10px) scale(0.995); }
|
|
4
|
+
to { opacity: 1; transform: translateX(0) scale(1); }
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
@media (min-width: 1024px) {
|
|
8
|
+
:is(#file-viewer, #terminal-container, #project-logs-panel, #issues-panel, #notes-browser, #scheduled-tasks-panel, #shared-browser-panel, #mcp-skills-workbench):not(.panel-fullscreen) {
|
|
9
|
+
box-shadow:
|
|
10
|
+
4px 8px 18px rgba(var(--shadow-rgb), 0.14),
|
|
11
|
+
2px 2px 4px rgba(var(--shadow-rgb), 0.08),
|
|
12
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
|
13
|
+
animation: workbench-panel-in 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
14
|
+
transition: max-width 0.2s ease, width 0.2s ease, box-shadow 0.2s ease;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
:is(#file-viewer, #terminal-container, #project-logs-panel, #issues-panel, #notes-browser, #scheduled-tasks-panel, #shared-browser-panel, #mcp-skills-workbench).panel-fullscreen {
|
|
19
|
+
box-shadow: none;
|
|
20
|
+
animation: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@media (prefers-reduced-motion: reduce) {
|
|
24
|
+
:is(#file-viewer, #terminal-container, #project-logs-panel, #issues-panel, #notes-browser, #scheduled-tasks-panel, #shared-browser-panel, #mcp-skills-workbench):not(.panel-fullscreen) {
|
|
25
|
+
animation: none;
|
|
26
|
+
transition: none;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
@media (min-width: 1024px) {
|
|
2
|
-
#scheduled-tasks-panel { width: 50%; max-width: 760px; min-width: 380px; height: calc(100% - 16px); min-height: 0; align-self: center; margin: 8px 8px 8px 10px; border: 1px solid var(--border); border-radius: 12px; background: var(--bg); display: flex; flex-direction: column; overflow: hidden; flex-shrink: 0;
|
|
2
|
+
#scheduled-tasks-panel { width: 50%; max-width: 760px; min-width: 380px; height: calc(100% - 16px); min-height: 0; align-self: center; margin: 8px 8px 8px 10px; border: 1px solid var(--border); border-radius: 12px; background: var(--bg); display: flex; flex-direction: column; overflow: hidden; flex-shrink: 0; }
|
|
3
3
|
#scheduled-tasks-panel.hidden { display: none; }
|
|
4
4
|
#scheduled-tasks-panel.scheduled-tasks-wide { width: 70%; max-width: 1200px; }
|
|
5
5
|
#scheduled-tasks-panel.panel-fullscreen { width: 100%; max-width: none; min-width: 0; height: 100%; margin: 0; border: 0; border-radius: 0; }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* Device silhouettes follow viewport dimensions; they do not emulate an OS. */
|
|
2
|
+
.shared-browser-viewport[data-skin="laptop"] .shared-browser-monitor { border: 9px solid #252729; border-bottom: 30px solid #c8cbcc; border-radius: 12px 12px 5px 5px; box-shadow: 0 0 0 2px #acb0b2, 0 20px 32px #0005; }
|
|
3
|
+
.shared-browser-viewport[data-skin="laptop"] .shared-browser-monitor-brand { left: 0; right: 0; text-align: center; color: #53585a; }
|
|
4
|
+
.shared-browser-viewport[data-skin="laptop"] .shared-browser-monitor-stand { bottom: -49px; height: 18px; width: calc(100% + 60px); background: linear-gradient(#dfe1e2, #babfc2); border-radius: 2px 2px 45% 45%; box-shadow: 0 3px 0 #858b8e, 0 12px 15px #0005; }
|
|
5
|
+
.shared-browser-viewport[data-skin="laptop"] .shared-browser-monitor-stand::before { top: 0; bottom: auto; height: 5px; left: 42%; right: 42%; clip-path: none; background: #9ba1a4; border-radius: 0 0 5px 5px; }
|
|
6
|
+
.shared-browser-viewport[data-skin="laptop"] .shared-browser-monitor-stand::after { display: none; }
|
|
7
|
+
.shared-browser-viewport[data-skin="crt"] { background: #183638; }
|
|
8
|
+
.shared-browser-viewport[data-skin="crt"] .shared-browser-monitor { border: 22px solid #c9c7b9; border-bottom-width: 42px; border-radius: 24px; box-shadow: inset 0 0 0 3px #797b72, 0 0 0 2px #e4e1d4, 8px 10px 0 #999b90, 12px 24px 30px #0005; }
|
|
9
|
+
.shared-browser-viewport[data-skin="crt"] .shared-browser-monitor::before { inset: -5px; border: 5px solid; border-color: #8a8b80 #b4b5a8 #e3e1d4 #9c9e91; border-radius: 6px; }
|
|
10
|
+
.shared-browser-viewport[data-skin="crt"] .shared-browser-monitor-brand { bottom: -33px; }
|
|
11
|
+
.shared-browser-viewport[data-skin="crt"] .shared-browser-monitor::after { bottom: -28px; right: 6px; width: 9px; height: 9px; border: 3px solid #afb1a3; }
|
|
12
|
+
.shared-browser-viewport[data-skin="crt"] .shared-browser-monitor-stand { bottom: -66px; width: 35%; height: 24px; }
|
|
13
|
+
.shared-browser-viewport[data-skin="crt"] .shared-browser-monitor-detail { position: absolute; bottom: -27px; right: 35px; width: 40px; height: 4px; background: repeating-linear-gradient(90deg, #85897e 0 6px, transparent 6px 12px); }
|
|
14
|
+
.shared-browser-viewport[data-skin="mac"] { background: #292824; gap: 28px; }
|
|
15
|
+
.shared-browser-viewport[data-skin="mac"] .shared-browser-monitor { border: 24px solid #d7cfbb; border-top-width: 30px; border-bottom-width: 78px; border-radius: 15px 15px 7px 7px; box-shadow: 0 5px 0 #afa68f, 0 7px 0 #e6ddc9, 0 22px 30px #0005; }
|
|
16
|
+
.shared-browser-viewport[data-skin="mac"] .shared-browser-monitor::before { inset: -6px; border: 6px solid; border-color: #aaa18c #bcb39d #ede4cf #bab099; border-radius: 9px; }
|
|
17
|
+
.shared-browser-viewport[data-skin="mac"] .shared-browser-monitor-brand { bottom: -51px; left: 1px; color: #696250; }
|
|
18
|
+
.shared-browser-viewport[data-skin="mac"] .shared-browser-monitor-stand { display: none; }
|
|
19
|
+
.shared-browser-viewport[data-skin="mac"] .shared-browser-monitor-detail { position: absolute; bottom: -43px; right: 2px; width: 30%; height: 5px; background: #625f54; border-bottom: 2px solid #efe6d3; border-radius: 1px; }
|
|
20
|
+
.shared-browser-viewport[data-skin="mac"] .shared-browser-monitor-detail::after { content: ""; position: absolute; top: 15px; right: 0; width: 65%; height: 10px; background: repeating-linear-gradient(90deg, #aaa08a 0 2px, transparent 2px 5px); }
|
|
21
|
+
.shared-browser-viewport[data-skin="mac"] .shared-browser-monitor::after { bottom: -67px; }
|
|
22
|
+
.shared-browser-viewport[data-skin="phone"] { background: #23282d; gap: 24px; }
|
|
23
|
+
.shared-browser-viewport[data-skin="phone"] .shared-browser-monitor { border: 12px solid #141517; border-top-width: 48px; border-bottom-width: 54px; border-radius: 38px; box-shadow: 0 0 0 2px #9a9d9f, 0 0 0 4px #46494c, 0 20px 35px #0006; }
|
|
24
|
+
.shared-browser-viewport[data-skin="phone"] .shared-browser-monitor::before { inset: auto auto -44px 50%; width: 32px; height: 32px; transform: translateX(-50%); border: 1px solid #666a6e; border-radius: 50%; background: #191b1d; box-shadow: inset 0 0 0 2px #111; }
|
|
25
|
+
.shared-browser-viewport[data-skin="phone"] .shared-browser-monitor::after { top: -27px; bottom: auto; right: calc(50% + 39px); width: 6px; height: 6px; background: #283d52; box-shadow: 0 0 0 2px #08090a; }
|
|
26
|
+
.shared-browser-viewport[data-skin="phone"] .shared-browser-monitor-detail { position: absolute; top: -26px; left: 50%; transform: translateX(-50%); width: 42px; height: 5px; border-radius: 4px; background: #08090a; box-shadow: 0 1px 0 #383b3d; }
|
|
27
|
+
.shared-browser-viewport[data-skin="phone"] .shared-browser-monitor-brand { left: auto; right: 10px; bottom: -34px; font-size: 10px; color: #92979c; }
|
|
28
|
+
.shared-browser-viewport[data-skin="phone"] .shared-browser-monitor-stand { display: none; }
|