clay-server 2.26.0-beta.4 → 2.26.0-beta.6
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/lib/browser-mcp-server.js +496 -0
- package/lib/project-mate-interaction.js +760 -0
- package/lib/project-memory.js +677 -0
- package/lib/project.js +381 -1364
- package/lib/public/app.js +289 -2
- package/lib/public/css/input.css +16 -0
- package/lib/public/css/overlays.css +181 -0
- package/lib/public/css/rewind.css +79 -0
- package/lib/public/css/server-settings.css +1 -0
- package/lib/public/css/title-bar.css +189 -3
- package/lib/public/index.html +23 -0
- package/lib/public/modules/context-sources.js +116 -29
- package/lib/public/modules/notifications.js +109 -1
- package/lib/sdk-bridge.js +22 -0
- package/lib/sdk-worker.js +13 -1
- package/lib/server.js +42 -0
- package/lib/sessions.js +16 -1
- package/package.json +2 -2
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
display: inline-flex;
|
|
24
24
|
align-items: center;
|
|
25
25
|
gap: 4px;
|
|
26
|
-
background: color-mix(in srgb,
|
|
27
|
-
color:
|
|
26
|
+
background: color-mix(in srgb, #3b82f6 12%, transparent);
|
|
27
|
+
color: #3b82f6;
|
|
28
28
|
border: none;
|
|
29
29
|
border-radius: 10px;
|
|
30
30
|
padding: 2px 10px;
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
.top-bar-update-btn .lucide { width: 12px; height: 12px; }
|
|
41
|
-
.top-bar-update-btn:hover { background: color-mix(in srgb,
|
|
41
|
+
.top-bar-update-btn:hover { background: color-mix(in srgb, #3b82f6 20%, transparent); }
|
|
42
42
|
|
|
43
43
|
/* --- Top bar actions (right-aligned group) --- */
|
|
44
44
|
.top-bar-actions {
|
|
@@ -342,6 +342,7 @@
|
|
|
342
342
|
gap: 6px;
|
|
343
343
|
width: 80px;
|
|
344
344
|
cursor: default;
|
|
345
|
+
position: relative;
|
|
345
346
|
}
|
|
346
347
|
|
|
347
348
|
.header-context-bar {
|
|
@@ -370,6 +371,191 @@
|
|
|
370
371
|
white-space: nowrap;
|
|
371
372
|
}
|
|
372
373
|
|
|
374
|
+
/* --- Context usage popover --- */
|
|
375
|
+
.context-usage-popover {
|
|
376
|
+
position: absolute;
|
|
377
|
+
top: calc(100% + 10px);
|
|
378
|
+
right: -12px;
|
|
379
|
+
width: 320px;
|
|
380
|
+
background: var(--bg-alt);
|
|
381
|
+
border: 1px solid var(--border);
|
|
382
|
+
border-radius: 10px;
|
|
383
|
+
padding: 16px;
|
|
384
|
+
font-size: 12px;
|
|
385
|
+
color: var(--text-secondary);
|
|
386
|
+
box-shadow: 0 4px 16px rgba(var(--shadow-rgb), 0.4);
|
|
387
|
+
z-index: 200;
|
|
388
|
+
animation: ctx-popover-in 0.12s ease-out;
|
|
389
|
+
line-height: 1.5;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.context-usage-popover.hidden { display: none; }
|
|
393
|
+
|
|
394
|
+
@keyframes ctx-popover-in {
|
|
395
|
+
from { opacity: 0; transform: translateY(-4px); }
|
|
396
|
+
to { opacity: 1; transform: translateY(0); }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/* Header */
|
|
400
|
+
.ctx-pop-header {
|
|
401
|
+
display: flex;
|
|
402
|
+
justify-content: space-between;
|
|
403
|
+
align-items: baseline;
|
|
404
|
+
margin-bottom: 12px;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.ctx-pop-model {
|
|
408
|
+
font-size: 12px;
|
|
409
|
+
color: var(--text-muted);
|
|
410
|
+
font-family: "Roboto Mono", monospace;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.ctx-pop-pct {
|
|
414
|
+
font-size: 22px;
|
|
415
|
+
font-weight: 600;
|
|
416
|
+
color: var(--text);
|
|
417
|
+
font-family: "Roboto Mono", monospace;
|
|
418
|
+
letter-spacing: -0.5px;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.ctx-pop-tokens {
|
|
422
|
+
font-size: 11px;
|
|
423
|
+
color: var(--text-muted);
|
|
424
|
+
font-family: "Roboto Mono", monospace;
|
|
425
|
+
margin-left: 6px;
|
|
426
|
+
font-weight: 400;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/* Stacked category bar */
|
|
430
|
+
.ctx-cat-bar {
|
|
431
|
+
display: flex;
|
|
432
|
+
height: 10px;
|
|
433
|
+
border-radius: 5px;
|
|
434
|
+
overflow: hidden;
|
|
435
|
+
background: var(--border);
|
|
436
|
+
margin-bottom: 12px;
|
|
437
|
+
gap: 1px;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.ctx-cat-bar > div {
|
|
441
|
+
height: 100%;
|
|
442
|
+
min-width: 3px;
|
|
443
|
+
transition: width 0.3s ease;
|
|
444
|
+
border-radius: 2px;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/* Category legend */
|
|
448
|
+
.ctx-cat-legend {
|
|
449
|
+
display: flex;
|
|
450
|
+
flex-direction: column;
|
|
451
|
+
gap: 1px;
|
|
452
|
+
margin-bottom: 4px;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.ctx-cat-item {
|
|
456
|
+
display: flex;
|
|
457
|
+
align-items: center;
|
|
458
|
+
justify-content: space-between;
|
|
459
|
+
width: 100%;
|
|
460
|
+
font-size: 12px;
|
|
461
|
+
padding: 3px 0;
|
|
462
|
+
border-radius: 4px;
|
|
463
|
+
transition: background 0.1s ease;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.ctx-cat-item:hover {
|
|
467
|
+
background: rgba(var(--overlay-rgb), 0.03);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.ctx-emoji {
|
|
471
|
+
display: inline-block;
|
|
472
|
+
filter: grayscale(1);
|
|
473
|
+
font-size: 12px;
|
|
474
|
+
transition: filter 0.15s ease;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.ctx-cat-item:hover .ctx-emoji,
|
|
478
|
+
.ctx-pop-row:hover .ctx-emoji,
|
|
479
|
+
.ctx-pop-note .ctx-emoji {
|
|
480
|
+
filter: grayscale(0);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.ctx-cat-name {
|
|
484
|
+
color: var(--text-secondary);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.ctx-cat-value {
|
|
488
|
+
font-family: "Roboto Mono", monospace;
|
|
489
|
+
color: var(--text-muted);
|
|
490
|
+
font-size: 11px;
|
|
491
|
+
font-variant-numeric: tabular-nums;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/* Sections */
|
|
495
|
+
.ctx-pop-divider {
|
|
496
|
+
border: none;
|
|
497
|
+
height: 1px;
|
|
498
|
+
background: var(--border-subtle);
|
|
499
|
+
margin: 10px 0;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.ctx-pop-section-label {
|
|
503
|
+
font-size: 11px;
|
|
504
|
+
text-transform: uppercase;
|
|
505
|
+
color: var(--text-muted);
|
|
506
|
+
letter-spacing: 0.8px;
|
|
507
|
+
margin-bottom: 6px;
|
|
508
|
+
font-weight: 500;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.ctx-pop-row {
|
|
512
|
+
display: flex;
|
|
513
|
+
justify-content: space-between;
|
|
514
|
+
align-items: center;
|
|
515
|
+
font-size: 12px;
|
|
516
|
+
padding: 3px 0;
|
|
517
|
+
border-radius: 4px;
|
|
518
|
+
transition: background 0.1s ease;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.ctx-pop-row:hover {
|
|
522
|
+
background: rgba(var(--overlay-rgb), 0.03);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.ctx-pop-row-label {
|
|
526
|
+
color: var(--text-secondary);
|
|
527
|
+
white-space: nowrap;
|
|
528
|
+
overflow: hidden;
|
|
529
|
+
text-overflow: ellipsis;
|
|
530
|
+
margin-right: 12px;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.ctx-pop-row-value {
|
|
534
|
+
font-family: "Roboto Mono", monospace;
|
|
535
|
+
color: var(--text-muted);
|
|
536
|
+
font-size: 11px;
|
|
537
|
+
flex-shrink: 0;
|
|
538
|
+
font-variant-numeric: tabular-nums;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.ctx-pop-note {
|
|
542
|
+
font-size: 11px;
|
|
543
|
+
color: var(--text-muted);
|
|
544
|
+
margin-top: 10px;
|
|
545
|
+
text-align: center;
|
|
546
|
+
padding: 4px 8px;
|
|
547
|
+
background: var(--border-subtle);
|
|
548
|
+
border-radius: 4px;
|
|
549
|
+
font-family: "Roboto Mono", monospace;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
@media (max-width: 400px) {
|
|
553
|
+
.context-usage-popover {
|
|
554
|
+
width: 260px;
|
|
555
|
+
right: -8px;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
373
559
|
/* --- Shared pill style for rate limit & fast mode --- */
|
|
374
560
|
.header-rate-limit,
|
|
375
561
|
.header-fast-mode {
|
package/lib/public/index.html
CHANGED
|
@@ -52,6 +52,28 @@
|
|
|
52
52
|
<div id="top-bar">
|
|
53
53
|
<div class="top-bar-left-pills">
|
|
54
54
|
<button id="pwa-install-pill" class="top-bar-install-btn hidden" title="Open as app"><i data-lucide="download"></i> Open as app</button>
|
|
55
|
+
<div id="ext-pill-wrap" class="ext-pill-wrap">
|
|
56
|
+
<button id="ext-pill" class="top-bar-ext-btn" title="Chrome Extension"><i data-lucide="puzzle"></i> Extension</button>
|
|
57
|
+
<div id="ext-popover" class="ext-popover">
|
|
58
|
+
<div class="ext-popover-header">
|
|
59
|
+
<div class="ext-popover-title">Clay for Chrome <span class="ext-experimental">Experimental</span></div>
|
|
60
|
+
<div class="ext-popover-sub">v0.1.0 · <a href="https://github.com/chadbyte/clay-chrome" target="_blank" rel="noopener">GitHub</a></div>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="ext-popover-connected hidden" id="ext-connected-banner"><i data-lucide="check-circle"></i> Extension connected</div>
|
|
63
|
+
<div class="ext-popover-desc" id="ext-popover-desc">Connect your browser to Clay. Gives Claude visibility into open tabs, console, network, DOM, and screenshots.</div>
|
|
64
|
+
<button class="ext-popover-download" id="ext-download-btn"><i data-lucide="download"></i> Download Extension (.zip)</button>
|
|
65
|
+
<div class="ext-popover-status hidden" id="ext-download-status"></div>
|
|
66
|
+
<div class="ext-popover-divider" id="ext-popover-divider"></div>
|
|
67
|
+
<div class="ext-popover-guide-title" id="ext-popover-guide-title">Installation</div>
|
|
68
|
+
<div class="ext-popover-steps">
|
|
69
|
+
<div class="ext-popover-step"><span class="ext-snum">1</span> Download above, then unzip to a permanent folder.</div>
|
|
70
|
+
<div class="ext-popover-step"><span class="ext-snum">2</span> Open <code id="ext-copy-url" class="ext-popover-code" title="Click to copy">chrome://extensions</code></div>
|
|
71
|
+
<div class="ext-popover-step"><span class="ext-snum">3</span> Enable <strong>Developer mode</strong> (top-right toggle).</div>
|
|
72
|
+
<div class="ext-popover-step"><span class="ext-snum">4</span> <strong>Load unpacked</strong> > select the unzipped folder.</div>
|
|
73
|
+
<div class="ext-popover-step"><span class="ext-snum">5</span> Refresh this page. Done!</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
55
77
|
<button id="share-pill" class="top-bar-share-btn" title="Share"><i data-lucide="qr-code"></i> Share</button>
|
|
56
78
|
<div id="update-pill-wrap" class="top-bar-update hidden">
|
|
57
79
|
<button id="update-pill" class="top-bar-update-btn"><i data-lucide="arrow-up-circle"></i> <span id="update-version"></span> is available. Update now</button>
|
|
@@ -420,6 +442,7 @@
|
|
|
420
442
|
<button id="context-sources-add" type="button" title="Add context source"><i data-lucide="plus"></i><span>Context Sources</span></button>
|
|
421
443
|
<div id="context-sources-picker" class="hidden">
|
|
422
444
|
<div class="context-picker-section" id="context-picker-terminals"></div>
|
|
445
|
+
<div class="context-picker-section" id="context-picker-tabs"></div>
|
|
423
446
|
</div>
|
|
424
447
|
</div>
|
|
425
448
|
<div id="suggestion-chips" class="hidden"></div>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
// Context Sources — attach terminal output
|
|
1
|
+
// Context Sources — attach terminal output and browser tabs as context for Claude
|
|
2
2
|
|
|
3
3
|
var ctx = null;
|
|
4
4
|
var activeSourceIds = new Set();
|
|
5
5
|
var terminalList = []; // synced from terminal module's term_list
|
|
6
|
+
var browserTabList = []; // synced from Chrome extension via postMessage
|
|
6
7
|
|
|
7
8
|
export function initContextSources(_ctx) {
|
|
8
9
|
ctx = _ctx;
|
|
@@ -79,6 +80,36 @@ export function updateTerminalList(terminals) {
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
// Called when Chrome extension sends tab list via postMessage
|
|
84
|
+
export function updateBrowserTabList(tabs) {
|
|
85
|
+
browserTabList = tabs || [];
|
|
86
|
+
|
|
87
|
+
// Remove active tab sources that no longer exist
|
|
88
|
+
var changed = false;
|
|
89
|
+
for (var id of activeSourceIds) {
|
|
90
|
+
if (id.startsWith("tab:")) {
|
|
91
|
+
var tabId = parseInt(id.split(":")[1], 10);
|
|
92
|
+
var found = false;
|
|
93
|
+
for (var i = 0; i < browserTabList.length; i++) {
|
|
94
|
+
if (browserTabList[i].id === tabId) { found = true; break; }
|
|
95
|
+
}
|
|
96
|
+
if (!found) {
|
|
97
|
+
activeSourceIds.delete(id);
|
|
98
|
+
changed = true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (changed) saveToServer();
|
|
104
|
+
renderChips();
|
|
105
|
+
|
|
106
|
+
// If picker is open, re-render it
|
|
107
|
+
var picker = document.getElementById("context-sources-picker");
|
|
108
|
+
if (!picker.classList.contains("hidden")) {
|
|
109
|
+
renderPicker();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
82
113
|
function toggleSource(sourceId) {
|
|
83
114
|
if (activeSourceIds.has(sourceId)) {
|
|
84
115
|
activeSourceIds.delete(sourceId);
|
|
@@ -150,42 +181,87 @@ function renderChips() {
|
|
|
150
181
|
}
|
|
151
182
|
|
|
152
183
|
function renderPicker() {
|
|
153
|
-
|
|
154
|
-
|
|
184
|
+
// --- Terminals section ---
|
|
185
|
+
var termSection = document.getElementById("context-picker-terminals");
|
|
186
|
+
termSection.innerHTML = "";
|
|
155
187
|
|
|
156
|
-
var
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
188
|
+
var termLabel = document.createElement("div");
|
|
189
|
+
termLabel.className = "context-picker-section-label";
|
|
190
|
+
termLabel.textContent = "Terminals";
|
|
191
|
+
termSection.appendChild(termLabel);
|
|
160
192
|
|
|
161
193
|
if (terminalList.length === 0) {
|
|
162
|
-
var
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
194
|
+
var termEmpty = document.createElement("div");
|
|
195
|
+
termEmpty.className = "context-picker-empty";
|
|
196
|
+
termEmpty.textContent = "No terminals open";
|
|
197
|
+
termSection.appendChild(termEmpty);
|
|
198
|
+
} else {
|
|
199
|
+
for (var i = 0; i < terminalList.length; i++) {
|
|
200
|
+
var term = terminalList[i];
|
|
201
|
+
var termSourceId = "term:" + term.id;
|
|
202
|
+
var termActive = activeSourceIds.has(termSourceId);
|
|
168
203
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
var isActive = activeSourceIds.has(sourceId);
|
|
204
|
+
var termItem = document.createElement("div");
|
|
205
|
+
termItem.className = "context-picker-item" + (termActive ? " active" : "");
|
|
206
|
+
termItem.setAttribute("data-source-id", termSourceId);
|
|
173
207
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
208
|
+
termItem.innerHTML =
|
|
209
|
+
'<i data-lucide="square-terminal"></i>' +
|
|
210
|
+
'<span>' + escapeHtml(term.title || ("Terminal " + term.id)) + '</span>' +
|
|
211
|
+
'<i data-lucide="check" class="context-picker-check"></i>';
|
|
177
212
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
213
|
+
termItem.addEventListener("click", function() {
|
|
214
|
+
toggleSource(this.getAttribute("data-source-id"));
|
|
215
|
+
if (typeof lucide !== "undefined") lucide.createIcons();
|
|
216
|
+
});
|
|
182
217
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
218
|
+
termSection.appendChild(termItem);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// --- Browser Tabs section ---
|
|
223
|
+
var tabSection = document.getElementById("context-picker-tabs");
|
|
224
|
+
tabSection.innerHTML = "";
|
|
225
|
+
|
|
226
|
+
if (browserTabList.length > 0) {
|
|
227
|
+
var tabLabel = document.createElement("div");
|
|
228
|
+
tabLabel.className = "context-picker-section-label";
|
|
229
|
+
tabLabel.textContent = "Browser Tabs";
|
|
230
|
+
tabSection.appendChild(tabLabel);
|
|
231
|
+
|
|
232
|
+
for (var j = 0; j < browserTabList.length; j++) {
|
|
233
|
+
var tab = browserTabList[j];
|
|
234
|
+
var tabSourceId = "tab:" + tab.id;
|
|
235
|
+
var tabActive = activeSourceIds.has(tabSourceId);
|
|
236
|
+
|
|
237
|
+
var tabItem = document.createElement("div");
|
|
238
|
+
tabItem.className = "context-picker-item" + (tabActive ? " active" : "");
|
|
239
|
+
tabItem.setAttribute("data-source-id", tabSourceId);
|
|
240
|
+
|
|
241
|
+
var tabTitle = tab.title || tab.url || "Tab";
|
|
242
|
+
// Truncate long URLs for display
|
|
243
|
+
var tabDisplay = tabTitle.length > 50 ? tabTitle.slice(0, 47) + "..." : tabTitle;
|
|
244
|
+
|
|
245
|
+
var faviconHtml = "";
|
|
246
|
+
if (tab.favIconUrl) {
|
|
247
|
+
faviconHtml = '<img src="' + escapeHtml(tab.favIconUrl) + '" class="context-picker-favicon" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'\'">' +
|
|
248
|
+
'<i data-lucide="globe" style="display:none"></i>';
|
|
249
|
+
} else {
|
|
250
|
+
faviconHtml = '<i data-lucide="globe"></i>';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
tabItem.innerHTML =
|
|
254
|
+
faviconHtml +
|
|
255
|
+
'<span title="' + escapeHtml(tab.url || "") + '">' + escapeHtml(tabDisplay) + '</span>' +
|
|
256
|
+
'<i data-lucide="check" class="context-picker-check"></i>';
|
|
257
|
+
|
|
258
|
+
tabItem.addEventListener("click", function() {
|
|
259
|
+
toggleSource(this.getAttribute("data-source-id"));
|
|
260
|
+
if (typeof lucide !== "undefined") lucide.createIcons();
|
|
261
|
+
});
|
|
187
262
|
|
|
188
|
-
|
|
263
|
+
tabSection.appendChild(tabItem);
|
|
264
|
+
}
|
|
189
265
|
}
|
|
190
266
|
|
|
191
267
|
if (typeof lucide !== "undefined") lucide.createIcons();
|
|
@@ -201,11 +277,22 @@ function getSourceLabel(id) {
|
|
|
201
277
|
}
|
|
202
278
|
return "Terminal " + termId;
|
|
203
279
|
}
|
|
280
|
+
if (id.startsWith("tab:")) {
|
|
281
|
+
var tabId = parseInt(id.split(":")[1], 10);
|
|
282
|
+
for (var j = 0; j < browserTabList.length; j++) {
|
|
283
|
+
if (browserTabList[j].id === tabId) {
|
|
284
|
+
var title = browserTabList[j].title || browserTabList[j].url || "";
|
|
285
|
+
return title.length > 30 ? title.slice(0, 27) + "..." : title;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return "Tab " + tabId;
|
|
289
|
+
}
|
|
204
290
|
return id;
|
|
205
291
|
}
|
|
206
292
|
|
|
207
293
|
function getSourceIcon(id) {
|
|
208
294
|
if (id.startsWith("term:")) return "square-terminal";
|
|
295
|
+
if (id.startsWith("tab:")) return "globe";
|
|
209
296
|
return "circle";
|
|
210
297
|
}
|
|
211
298
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { copyToClipboard } from './utils.js';
|
|
1
|
+
import { copyToClipboard, showToast } from './utils.js';
|
|
2
2
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
3
3
|
|
|
4
4
|
var ctx;
|
|
@@ -155,6 +155,114 @@ export function initNotifications(_ctx) {
|
|
|
155
155
|
});
|
|
156
156
|
})();
|
|
157
157
|
|
|
158
|
+
// --- Extension pill popover ---
|
|
159
|
+
(function () {
|
|
160
|
+
var extPillWrap = $("ext-pill-wrap");
|
|
161
|
+
var extPillBtn = $("ext-pill");
|
|
162
|
+
var extPopover = $("ext-popover");
|
|
163
|
+
var extDownloadBtn = $("ext-download-btn");
|
|
164
|
+
var extDownloadStatus = $("ext-download-status");
|
|
165
|
+
var extCopyUrl = $("ext-copy-url");
|
|
166
|
+
if (!extPillWrap || !extPillBtn || !extPopover) return;
|
|
167
|
+
|
|
168
|
+
// Detect extension connection via postMessage from content.js
|
|
169
|
+
var extConnected = false;
|
|
170
|
+
var connectedBanner = $("ext-connected-banner");
|
|
171
|
+
var extDesc = $("ext-popover-desc");
|
|
172
|
+
var extDivider = $("ext-popover-divider");
|
|
173
|
+
var extGuideTitle = $("ext-popover-guide-title");
|
|
174
|
+
var extSteps = extPopover.querySelector(".ext-popover-steps");
|
|
175
|
+
|
|
176
|
+
function setExtConnected() {
|
|
177
|
+
if (extConnected) return;
|
|
178
|
+
extConnected = true;
|
|
179
|
+
extPillBtn.classList.add("ext-connected");
|
|
180
|
+
extPillBtn.innerHTML = '<i data-lucide="check"></i> Extension';
|
|
181
|
+
refreshIcons(extPillBtn);
|
|
182
|
+
if (connectedBanner) connectedBanner.classList.remove("hidden");
|
|
183
|
+
if (extDownloadBtn) extDownloadBtn.style.display = "none";
|
|
184
|
+
if (extDownloadStatus) extDownloadStatus.classList.add("hidden");
|
|
185
|
+
if (extDesc) extDesc.style.display = "none";
|
|
186
|
+
if (extDivider) extDivider.style.display = "none";
|
|
187
|
+
if (extGuideTitle) extGuideTitle.style.display = "none";
|
|
188
|
+
if (extSteps) extSteps.style.display = "none";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
window.addEventListener("message", function (event) {
|
|
192
|
+
if (event.source !== window) return;
|
|
193
|
+
if (!event.data || event.data.source !== "clay-chrome-extension") return;
|
|
194
|
+
setExtConnected();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Toggle popover
|
|
198
|
+
extPillBtn.addEventListener("click", function (e) {
|
|
199
|
+
e.stopPropagation();
|
|
200
|
+
extPopover.classList.toggle("visible");
|
|
201
|
+
refreshIcons(extPopover);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
document.addEventListener("click", function (e) {
|
|
205
|
+
if (!extPopover.contains(e.target) && e.target !== extPillBtn && !extPillBtn.contains(e.target)) {
|
|
206
|
+
extPopover.classList.remove("visible");
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Download button
|
|
211
|
+
if (extDownloadBtn) {
|
|
212
|
+
extDownloadBtn.addEventListener("click", function (e) {
|
|
213
|
+
e.stopPropagation();
|
|
214
|
+
extDownloadBtn.disabled = true;
|
|
215
|
+
extDownloadBtn.innerHTML = iconHtml("loader") + " Downloading...";
|
|
216
|
+
refreshIcons(extDownloadBtn);
|
|
217
|
+
var loaderIcon = extDownloadBtn.querySelector(".lucide");
|
|
218
|
+
if (loaderIcon) loaderIcon.style.animation = "spin 1s linear infinite";
|
|
219
|
+
if (extDownloadStatus) {
|
|
220
|
+
extDownloadStatus.classList.remove("hidden");
|
|
221
|
+
extDownloadStatus.textContent = "Fetching from GitHub...";
|
|
222
|
+
extDownloadStatus.style.color = "";
|
|
223
|
+
}
|
|
224
|
+
fetch("/api/extension/download").then(function (resp) {
|
|
225
|
+
if (!resp.ok) throw new Error("Download failed (" + resp.status + ")");
|
|
226
|
+
return resp.blob();
|
|
227
|
+
}).then(function (blob) {
|
|
228
|
+
var url = URL.createObjectURL(blob);
|
|
229
|
+
var a = document.createElement("a");
|
|
230
|
+
a.href = url;
|
|
231
|
+
a.download = "clay-chrome-extension.zip";
|
|
232
|
+
document.body.appendChild(a);
|
|
233
|
+
a.click();
|
|
234
|
+
document.body.removeChild(a);
|
|
235
|
+
URL.revokeObjectURL(url);
|
|
236
|
+
if (extDownloadStatus) {
|
|
237
|
+
extDownloadStatus.textContent = "Download complete!";
|
|
238
|
+
extDownloadStatus.style.color = "var(--accent)";
|
|
239
|
+
}
|
|
240
|
+
showToast("Extension downloaded");
|
|
241
|
+
}).catch(function (err) {
|
|
242
|
+
if (extDownloadStatus) {
|
|
243
|
+
extDownloadStatus.textContent = "Failed: " + err.message;
|
|
244
|
+
extDownloadStatus.style.color = "var(--danger, #e53935)";
|
|
245
|
+
}
|
|
246
|
+
showToast("Download failed");
|
|
247
|
+
}).finally(function () {
|
|
248
|
+
extDownloadBtn.disabled = false;
|
|
249
|
+
extDownloadBtn.innerHTML = iconHtml("download") + " Download Extension (.zip)";
|
|
250
|
+
refreshIcons(extDownloadBtn);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Copy chrome://extensions URL
|
|
256
|
+
if (extCopyUrl) {
|
|
257
|
+
extCopyUrl.addEventListener("click", function (e) {
|
|
258
|
+
e.stopPropagation();
|
|
259
|
+
copyToClipboard("chrome://extensions").then(function () {
|
|
260
|
+
showToast("Copied chrome://extensions");
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
})();
|
|
265
|
+
|
|
158
266
|
// --- Settings: Check for updates ---
|
|
159
267
|
(function () {
|
|
160
268
|
var settingsUpdateCheck = $("settings-update-check");
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -134,6 +134,7 @@ function createSDKBridge(opts) {
|
|
|
134
134
|
var mateDisplayName = opts.mateDisplayName || "";
|
|
135
135
|
var isMate = opts.isMate || (slug.indexOf("mate-") === 0);
|
|
136
136
|
var dangerouslySkipPermissions = opts.dangerouslySkipPermissions || false;
|
|
137
|
+
var mcpServers = opts.mcpServers || null;
|
|
137
138
|
var onProcessingChanged = opts.onProcessingChanged || function () {};
|
|
138
139
|
var onTurnDone = opts.onTurnDone || null;
|
|
139
140
|
|
|
@@ -188,6 +189,10 @@ function createSDKBridge(opts) {
|
|
|
188
189
|
sm.sendAndRecord(session, obj);
|
|
189
190
|
}
|
|
190
191
|
|
|
192
|
+
function sendToSession(session, obj) {
|
|
193
|
+
sm.sendToSession(session, obj);
|
|
194
|
+
}
|
|
195
|
+
|
|
191
196
|
function processSDKMessage(session, parsed) {
|
|
192
197
|
// Timing: log key SDK milestones relative to query start
|
|
193
198
|
if (session._queryStartTs) {
|
|
@@ -435,6 +440,15 @@ function createSDKBridge(opts) {
|
|
|
435
440
|
session.isProcessing = false;
|
|
436
441
|
session.rateLimitResetsAt = null; // clear on success
|
|
437
442
|
onProcessingChanged();
|
|
443
|
+
// Fetch rich context usage breakdown (fire-and-forget, non-blocking)
|
|
444
|
+
if (session.queryInstance && typeof session.queryInstance.getContextUsage === "function") {
|
|
445
|
+
session.queryInstance.getContextUsage().then(function(ctxUsage) {
|
|
446
|
+
session.lastContextUsage = ctxUsage;
|
|
447
|
+
sendToSession(session, { type: "context_usage", data: ctxUsage });
|
|
448
|
+
}).catch(function(e) {
|
|
449
|
+
console.error("[sdk-bridge] getContextUsage failed (non-fatal):", e.message || e);
|
|
450
|
+
});
|
|
451
|
+
}
|
|
438
452
|
var lastStreamInput = session.lastStreamInputTokens || null;
|
|
439
453
|
session.lastStreamInputTokens = null;
|
|
440
454
|
sendAndRecord(session, {
|
|
@@ -1115,6 +1129,7 @@ function createSDKBridge(opts) {
|
|
|
1115
1129
|
agentProgressSummaries: true,
|
|
1116
1130
|
};
|
|
1117
1131
|
|
|
1132
|
+
if (mcpServers) queryOptions.mcpServers = mcpServers;
|
|
1118
1133
|
if (sm.currentModel) queryOptions.model = sm.currentModel;
|
|
1119
1134
|
if (sm.currentEffort) queryOptions.effort = sm.currentEffort;
|
|
1120
1135
|
if (sm.currentBetas && sm.currentBetas.length > 0) queryOptions.betas = sm.currentBetas;
|
|
@@ -1196,6 +1211,11 @@ function createSDKBridge(opts) {
|
|
|
1196
1211
|
});
|
|
1197
1212
|
break;
|
|
1198
1213
|
|
|
1214
|
+
case "context_usage":
|
|
1215
|
+
session.lastContextUsage = msg.data;
|
|
1216
|
+
sendToSession(session, { type: "context_usage", data: msg.data });
|
|
1217
|
+
break;
|
|
1218
|
+
|
|
1199
1219
|
case "query_done":
|
|
1200
1220
|
console.log("[sdk-bridge] IPC query_done received, pid=" + (worker.process ? worker.process.pid : "?"));
|
|
1201
1221
|
// Mark that we received a proper IPC completion, so the exit
|
|
@@ -1698,6 +1718,7 @@ function createSDKBridge(opts) {
|
|
|
1698
1718
|
for await (var msg of myQueryInstance) {
|
|
1699
1719
|
processSDKMessage(session, msg);
|
|
1700
1720
|
}
|
|
1721
|
+
// (getContextUsage moved to processSDKMessage result handler -- fire-and-forget)
|
|
1701
1722
|
// Stream ended normally after a task stop — no "result" message was sent,
|
|
1702
1723
|
// so the session is still marked as processing. Send interrupted feedback.
|
|
1703
1724
|
if (session.isProcessing && session.taskStopRequested) {
|
|
@@ -1921,6 +1942,7 @@ function createSDKBridge(opts) {
|
|
|
1921
1942
|
abortController: session.abortController,
|
|
1922
1943
|
promptSuggestions: true,
|
|
1923
1944
|
agentProgressSummaries: true,
|
|
1945
|
+
mcpServers: mcpServers || undefined,
|
|
1924
1946
|
canUseTool: function(toolName, input, toolOpts) {
|
|
1925
1947
|
return handleCanUseTool(session, toolName, input, toolOpts);
|
|
1926
1948
|
},
|
package/lib/sdk-worker.js
CHANGED
|
@@ -351,7 +351,19 @@ async function handleQueryStart(msg) {
|
|
|
351
351
|
}
|
|
352
352
|
sendToDaemon({ type: "sdk_event", event: event });
|
|
353
353
|
}
|
|
354
|
-
perf("all events streamed (counts=" + JSON.stringify(eventCounts) + "),
|
|
354
|
+
perf("all events streamed (counts=" + JSON.stringify(eventCounts) + "), fetching context usage");
|
|
355
|
+
// Fetch context usage breakdown before queryInstance is cleared
|
|
356
|
+
try {
|
|
357
|
+
if (queryInstance && typeof queryInstance.getContextUsage === "function") {
|
|
358
|
+
var ctxUsage = await queryInstance.getContextUsage();
|
|
359
|
+
sendToDaemon({ type: "context_usage", data: ctxUsage });
|
|
360
|
+
perf("context usage sent");
|
|
361
|
+
}
|
|
362
|
+
} catch (e) {
|
|
363
|
+
// Non-fatal: SDK may have already shut down
|
|
364
|
+
console.error("[sdk-worker] getContextUsage failed (non-fatal):", e.message);
|
|
365
|
+
}
|
|
366
|
+
perf("sending query_done");
|
|
355
367
|
sendToDaemon({ type: "query_done" });
|
|
356
368
|
} catch (err) {
|
|
357
369
|
var errMsg = err.message || String(err);
|